]> git.sesse.net Git - vlc/blob - modules/codec/zvbi.c
zvbi: strip leading/end whitespaces from text subtitles
[vlc] / modules / codec / zvbi.c
1 /*****************************************************************************
2  * zvbi.c : VBI and Teletext PES demux and decoder using libzvbi
3  *****************************************************************************
4  * Copyright (C) 2007, M2X
5  * $Id$
6  *
7  * Authors: Derk-Jan Hartman <djhartman at m2x dot nl>
8  *          Jean-Paul Saman <jpsaman at m2x dot nl>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  *
27  * information on teletext format can be found here :
28  * http://pdc.ro.nu/teletext.html
29  *
30  *****************************************************************************/
31
32 /* This module implements:
33  * ETSI EN 301 775: VBI data in PES
34  * ETSI EN 300 472: EBU Teletext data in PES
35  * ETSI EN 300 706: Enhanced Teletext (libzvbi)
36  * ETSI EN 300 231: Video Programme System [VPS] (libzvbi)
37  * ETSI EN 300 294: 625-line Wide Screen Signaling [WSS] (libzvbi)
38  * EIA-608 Revision A: Closed Captioning [CC] (libzvbi)
39  */
40
41 #ifdef HAVE_CONFIG_H
42 # include "config.h"
43 #endif
44
45 #include <ctype.h>
46
47 #include <vlc_common.h>
48 #include <vlc_plugin.h>
49 #include <assert.h>
50 #include <libzvbi.h>
51
52 #include <vlc_codec.h>
53
54 /*****************************************************************************
55  * Module descriptor.
56  *****************************************************************************/
57 static int  Open ( vlc_object_t * );
58 static void Close( vlc_object_t * );
59
60 #define PAGE_TEXT N_("Teletext page")
61 #define PAGE_LONGTEXT N_("Open the indicated Teletext page." \
62         "Default page is index 100")
63
64 #define OPAQUE_TEXT N_("Teletext transparency")
65 #define OPAQUE_LONGTEXT N_("Setting vbi-opaque to false " \
66         "makes the boxed text transparent." )
67
68 #define POS_TEXT N_("Teletext alignment")
69 #define POS_LONGTEXT N_( \
70   "You can enforce the teletext position on the video " \
71   "(0=center, 1=left, 2=right, 4=top, 8=bottom, you can " \
72   "also use combinations of these values, eg. 6 = top-right).")
73
74 #define TELX_TEXT N_("Teletext text subtitles")
75 #define TELX_LONGTEXT N_( "Output teletext subtitles as text " \
76   "instead of as RGBA" )
77
78 static const int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
79 static const char *const ppsz_pos_descriptions[] =
80 { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
81   N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
82
83 vlc_module_begin ()
84     set_description( N_("VBI and Teletext decoder") )
85     set_shortname( N_("VBI & Teletext") )
86     set_capability( "decoder", 51 )
87     set_category( CAT_INPUT )
88     set_subcategory( SUBCAT_INPUT_SCODEC )
89     set_callbacks( Open, Close )
90
91     add_integer( "vbi-page", 100,
92                  PAGE_TEXT, PAGE_LONGTEXT, false )
93     add_bool( "vbi-opaque", true,
94                  OPAQUE_TEXT, OPAQUE_LONGTEXT, false )
95     add_integer( "vbi-position", 8, POS_TEXT, POS_LONGTEXT, false )
96         change_integer_list( pi_pos_values, ppsz_pos_descriptions );
97     add_bool( "vbi-text", false,
98               TELX_TEXT, TELX_LONGTEXT, false )
99 vlc_module_end ()
100
101 /****************************************************************************
102  * Local structures
103  ****************************************************************************/
104
105 // #define ZVBI_DEBUG
106
107 //Guessing table for missing "default region triplet"
108 static const int pi_default_triplet[] = {
109  0, 0, 0, 0,     // slo slk cze ces
110  8,              // pol
111  24,24,24,24,24, //scc scr srp hrv slv
112  24,24,          //rum ron
113  32,32,32,32,32, //est lit rus bul ukr
114  48,48,          //gre ell
115  64,             //ara
116  88,             //heb
117  16 };           //default
118 static const char *const ppsz_default_triplet[] = {
119  "slo", "slk", "cze", "ces",
120  "pol",
121  "scc", "scr", "srp", "hrv", "slv",
122  "rum", "ron",
123  "est", "lit", "rus", "bul", "ukr",
124  "gre", "ell",
125  "ara",
126  "heb",
127  NULL
128 };
129
130 typedef enum {
131     ZVBI_KEY_RED    = 'r' << 16,
132     ZVBI_KEY_GREEN  = 'g' << 16,
133     ZVBI_KEY_YELLOW = 'y' << 16,
134     ZVBI_KEY_BLUE   = 'b' << 16,
135     ZVBI_KEY_INDEX  = 'i' << 16,
136 } ttxt_key_id;
137
138 #define MAX_SLICES 32
139
140 struct decoder_sys_t
141 {
142     vbi_decoder *     p_vbi_dec;
143     vbi_sliced        p_vbi_sliced[MAX_SLICES];
144     unsigned int      i_last_page;
145     bool              b_update;
146     bool              b_text;   /* Subtitles as text */
147
148     vlc_mutex_t       lock; /* Lock to protect the following variables */
149     /* Positioning of Teletext images */
150     int               i_align;
151     /* */
152     unsigned int      i_wanted_page;
153     unsigned int      i_wanted_subpage;
154     /* */
155     bool              b_opaque;
156     struct {
157         int pgno, subno;
158     }                 nav_link[6];
159     int               i_key[3];
160 };
161
162 static subpicture_t *Decode( decoder_t *, block_t ** );
163
164 static subpicture_t *Subpicture( decoder_t *p_dec, video_format_t *p_fmt,
165                                  bool b_text,
166                                  int i_columns, int i_rows,
167                                  int i_align, mtime_t i_pts );
168
169 static void EventHandler( vbi_event *ev, void *user_data );
170 static int OpaquePage( picture_t *p_src, const vbi_page *p_page,
171                        const video_format_t fmt, bool b_opaque, const int text_offset );
172 static int get_first_visible_row( vbi_char *p_text, int rows, int columns);
173 static int get_last_visible_row( vbi_char *p_text, int rows, int columns);
174
175 /* Properties callbacks */
176 static int RequestPage( vlc_object_t *p_this, char const *psz_cmd,
177                         vlc_value_t oldval, vlc_value_t newval, void *p_data );
178 static int Opaque( vlc_object_t *p_this, char const *psz_cmd,
179                    vlc_value_t oldval, vlc_value_t newval, void *p_data );
180 static int Position( vlc_object_t *p_this, char const *psz_cmd,
181                      vlc_value_t oldval, vlc_value_t newval, void *p_data );
182 static int EventKey( vlc_object_t *p_this, char const *psz_cmd,
183                      vlc_value_t oldval, vlc_value_t newval, void *p_data );
184
185 /*****************************************************************************
186  * Open: probe the decoder and return score
187  *****************************************************************************
188  * Tries to launch a decoder and return score so that the interface is able
189  * to chose.
190  *****************************************************************************/
191 static int Open( vlc_object_t *p_this )
192 {
193     decoder_t     *p_dec = (decoder_t *) p_this;
194     decoder_sys_t *p_sys = NULL;
195
196     if( p_dec->fmt_in.i_codec != VLC_CODEC_TELETEXT )
197         return VLC_EGENERIC;
198
199     p_sys = p_dec->p_sys = calloc( 1, sizeof(decoder_sys_t) );
200     if( p_sys == NULL )
201         return VLC_ENOMEM;
202
203     p_sys->i_key[0] = p_sys->i_key[1] = p_sys->i_key[2] = '*' - '0';
204     p_sys->b_update = false;
205     p_sys->p_vbi_dec = vbi_decoder_new();
206     vlc_mutex_init( &p_sys->lock );
207
208     if( p_sys->p_vbi_dec == NULL )
209     {
210         msg_Err( p_dec, "VBI decoder could not be created." );
211         Close( p_this );
212         return VLC_ENOMEM;
213     }
214
215     /* Some broadcasters in countries with level 1 and level 1.5 still not send a G0 to do 
216      * matches against table 32 of ETSI 300 706. We try to do some best effort guessing
217      * This is not perfect, but might handle some cases where we know the vbi language 
218      * is known. It would be better if people started sending G0 */
219     for( int i = 0; ppsz_default_triplet[i] != NULL; i++ )
220     {
221         if( p_dec->fmt_in.psz_language && !strcasecmp( p_dec->fmt_in.psz_language, ppsz_default_triplet[i] ) )
222         {
223             vbi_teletext_set_default_region( p_sys->p_vbi_dec, pi_default_triplet[i]);
224             msg_Dbg( p_dec, "overwriting default zvbi region: %d", pi_default_triplet[i] );
225         }
226     }
227
228     vbi_event_handler_register( p_sys->p_vbi_dec, VBI_EVENT_TTX_PAGE | VBI_EVENT_NETWORK |
229 #ifdef ZVBI_DEBUG
230                                 VBI_EVENT_CAPTION | VBI_EVENT_TRIGGER |
231                                 VBI_EVENT_ASPECT | VBI_EVENT_PROG_INFO | VBI_EVENT_NETWORK_ID |
232 #endif
233                                 0 , EventHandler, p_dec );
234
235     /* Create the var on vlc_global. */
236     p_sys->i_wanted_page = var_CreateGetInteger( p_dec, "vbi-page" );
237     var_AddCallback( p_dec, "vbi-page", RequestPage, p_sys );
238
239     /* Check if the Teletext track has a known "initial page". */
240     if( p_sys->i_wanted_page == 100 && p_dec->fmt_in.subs.teletext.i_magazine != -1 )
241     {
242         p_sys->i_wanted_page = 100 * p_dec->fmt_in.subs.teletext.i_magazine +
243                                vbi_bcd2dec( p_dec->fmt_in.subs.teletext.i_page );
244         var_SetInteger( p_dec, "vbi-page", p_sys->i_wanted_page );
245     }
246     p_sys->i_wanted_subpage = VBI_ANY_SUBNO;
247
248     p_sys->b_opaque = var_CreateGetBool( p_dec, "vbi-opaque" );
249     var_AddCallback( p_dec, "vbi-opaque", Opaque, p_sys );
250
251     p_sys->i_align = var_CreateGetInteger( p_dec, "vbi-position" );
252     var_AddCallback( p_dec, "vbi-position", Position, p_sys );
253
254     p_sys->b_text = var_CreateGetBool( p_dec, "vbi-text" );
255 //    var_AddCallback( p_dec, "vbi-text", Text, p_sys );
256
257     /* Listen for keys */
258     var_AddCallback( p_dec->p_libvlc, "key-pressed", EventKey, p_dec );
259
260     es_format_Init( &p_dec->fmt_out, SPU_ES, VLC_CODEC_SPU );
261     if( p_sys->b_text )
262         p_dec->fmt_out.video.i_chroma = VLC_CODEC_TEXT;
263     else
264         p_dec->fmt_out.video.i_chroma = VLC_CODEC_RGBA;
265
266     p_dec->pf_decode_sub = Decode;
267     return VLC_SUCCESS;
268 }
269
270 /*****************************************************************************
271  * Close:
272  *****************************************************************************/
273 static void Close( vlc_object_t *p_this )
274 {
275     decoder_t     *p_dec = (decoder_t*) p_this;
276     decoder_sys_t *p_sys = p_dec->p_sys;
277
278     var_DelCallback( p_dec, "vbi-position", Position, p_sys );
279     var_DelCallback( p_dec, "vbi-opaque", Opaque, p_sys );
280     var_DelCallback( p_dec, "vbi-page", RequestPage, p_sys );
281     var_DelCallback( p_dec->p_libvlc, "key-pressed", EventKey, p_dec );
282
283     vlc_mutex_destroy( &p_sys->lock );
284
285     if( p_sys->p_vbi_dec )
286         vbi_decoder_delete( p_sys->p_vbi_dec );
287     free( p_sys );
288 }
289
290 #ifdef WORDS_BIGENDIAN
291 # define ZVBI_PIXFMT_RGBA32 VBI_PIXFMT_RGBA32_BE
292 #else
293 # define ZVBI_PIXFMT_RGBA32 VBI_PIXFMT_RGBA32_LE
294 #endif
295
296 /*****************************************************************************
297  * Decode:
298  *****************************************************************************/
299 static subpicture_t *Decode( decoder_t *p_dec, block_t **pp_block )
300 {
301     decoder_sys_t   *p_sys = p_dec->p_sys;
302     block_t         *p_block;
303     subpicture_t    *p_spu = NULL;
304     video_format_t  fmt;
305     bool            b_cached = false;
306     vbi_page        p_page;
307
308     if( (pp_block == NULL) || (*pp_block == NULL) )
309         return NULL;
310
311     p_block = *pp_block;
312     *pp_block = NULL;
313
314     if( p_block->i_buffer > 0 &&
315         ( ( p_block->p_buffer[0] >= 0x10 && p_block->p_buffer[0] <= 0x1f ) ||
316           ( p_block->p_buffer[0] >= 0x99 && p_block->p_buffer[0] <= 0x9b ) ) )
317     {
318         vbi_sliced   *p_sliced = p_sys->p_vbi_sliced;
319         unsigned int i_lines = 0;
320
321         p_block->i_buffer--;
322         p_block->p_buffer++;
323         while( p_block->i_buffer >= 2 )
324         {
325             int      i_id   = p_block->p_buffer[0];
326             unsigned i_size = p_block->p_buffer[1];
327
328             if( 2 + i_size > p_block->i_buffer )
329                 break;
330
331             if( ( i_id == 0x02 || i_id == 0x03 ) && i_size >= 44 && i_lines < MAX_SLICES )
332             {
333                 unsigned line_offset  = p_block->p_buffer[2] & 0x1f;
334                 unsigned field_parity = p_block->p_buffer[2] & 0x20;
335
336                 p_sliced[i_lines].id = VBI_SLICED_TELETEXT_B;
337                 if( line_offset > 0 )
338                     p_sliced[i_lines].line = line_offset + (field_parity ? 0 : 313);
339                 else
340                     p_sliced[i_lines].line = 0;
341                 for( int i = 0; i < 42; i++ )
342                     p_sliced[i_lines].data[i] = vbi_rev8( p_block->p_buffer[4 + i] );
343                 i_lines++;
344             }
345
346             p_block->i_buffer -= 2 + i_size;
347             p_block->p_buffer += 2 + i_size;
348         }
349
350         if( i_lines > 0 )
351             vbi_decode( p_sys->p_vbi_dec, p_sliced, i_lines, 0 );
352     }
353
354     /* */
355     vlc_mutex_lock( &p_sys->lock );
356     const int i_align = p_sys->i_align;
357     const unsigned int i_wanted_page = p_sys->i_wanted_page;
358     const unsigned int i_wanted_subpage = p_sys->i_wanted_subpage;
359     const bool b_opaque = p_sys->b_opaque;
360     vlc_mutex_unlock( &p_sys->lock );
361
362     /* Try to see if the page we want is in the cache yet */
363     memset( &p_page, 0, sizeof(vbi_page) );
364     b_cached = vbi_fetch_vt_page( p_sys->p_vbi_dec, &p_page,
365                                   vbi_dec2bcd( i_wanted_page ),
366                                   i_wanted_subpage, VBI_WST_LEVEL_3p5,
367                                   25, true );
368
369     if( i_wanted_page == p_sys->i_last_page && !p_sys->b_update )
370         goto error;
371
372     if( !b_cached )
373     {
374         if( p_sys->i_last_page != i_wanted_page )
375         {
376             /* We need to reset the subtitle */
377             p_spu = Subpicture( p_dec, &fmt, true,
378                                 p_page.columns, p_page.rows,
379                                 i_align, p_block->i_pts );
380             if( !p_spu )
381                 goto error;
382             p_spu->p_region->psz_text = strdup("");
383
384             p_sys->b_update = true;
385             p_sys->i_last_page = i_wanted_page;
386             goto exit;
387         }
388         goto error;
389     }
390
391     p_sys->b_update = false;
392     p_sys->i_last_page = i_wanted_page;
393 #ifdef ZVBI_DEBUG
394     msg_Dbg( p_dec, "we now have page: %d ready for display",
395              i_wanted_page );
396 #endif
397
398     /* Ignore transparent rows at the beginning and end */
399     int i_first_row = get_first_visible_row( p_page.text, p_page.rows, p_page.columns );
400     if ( i_first_row < 0 )
401         goto error;
402     int i_num_rows = get_last_visible_row( p_page.text, p_page.rows, p_page.columns ) - i_first_row + 1;
403 #ifdef ZVBI_DEBUG
404     msg_Dbg( p_dec, "After top and tail of page we have rows %i-%i of %i",
405              i_first_row + 1, i_first_row + i_num_rows, p_page.rows );
406 #endif
407
408     /* If there is a page or sub to render, then we do that here */
409     /* Create the subpicture unit */
410     p_spu = Subpicture( p_dec, &fmt, p_sys->b_text,
411                         p_page.columns, i_num_rows,
412                         i_align, p_block->i_pts );
413     if( !p_spu )
414         goto error;
415
416     if( p_sys->b_text )
417     {
418         unsigned int i_textsize = 7000;
419         int i_total,offset;
420         char p_text[i_textsize+1];
421
422         i_total = vbi_print_page_region( &p_page, p_text, i_textsize,
423                         "UTF-8", 0, 0, 0, i_first_row, p_page.columns, i_num_rows );
424
425         for( offset=1; offset<i_total && isspace( p_text[i_total-offset ] ); offset++)
426            p_text[i_total-offset] = '\0';
427
428         i_total -= offset;
429
430         offset=0;
431         while( offset < i_total && isspace( p_text[offset] ) )
432            offset++;
433
434         p_spu->p_region->psz_text = strdup( &p_text[offset] );
435 #ifdef ZVBI_DEBUG
436         msg_Info( p_dec, "page %x-%x(%d)\n\"%s\"", p_page.pgno, p_page.subno, i_total, &p_text[offset] );
437 #endif
438     }
439     else
440     {
441         picture_t *p_pic = p_spu->p_region->p_picture;
442
443         /* ZVBI is stupid enough to assume pitch == width */
444         p_pic->p->i_pitch = 4 * fmt.i_width;
445
446         /* Maintain subtitle postion */
447         p_spu->p_region->i_y = i_first_row*10;
448         p_spu->i_original_picture_width = p_page.columns*12;
449         p_spu->i_original_picture_height = p_page.rows*10;
450
451         vbi_draw_vt_page_region( &p_page, ZVBI_PIXFMT_RGBA32,
452                           p_spu->p_region->p_picture->p->p_pixels, -1,
453                           0, i_first_row, p_page.columns, i_num_rows,
454                           1, 1);
455
456         vlc_mutex_lock( &p_sys->lock );
457         memcpy( p_sys->nav_link, &p_page.nav_link, sizeof( p_sys->nav_link )) ;
458         vlc_mutex_unlock( &p_sys->lock );
459
460         OpaquePage( p_pic, &p_page, fmt, b_opaque, i_first_row * p_page.columns );
461     }
462
463 exit:
464     vbi_unref_page( &p_page );
465     block_Release( p_block );
466     return p_spu;
467
468 error:
469     vbi_unref_page( &p_page );
470     if( p_spu != NULL )
471     {
472         decoder_DeleteSubpicture( p_dec, p_spu );
473         p_spu = NULL;
474     }
475
476     block_Release( p_block );
477     return NULL;
478 }
479
480 static subpicture_t *Subpicture( decoder_t *p_dec, video_format_t *p_fmt,
481                                  bool b_text,
482                                  int i_columns, int i_rows, int i_align,
483                                  mtime_t i_pts )
484 {
485     video_format_t fmt;
486     subpicture_t *p_spu;
487
488     /* If there is a page or sub to render, then we do that here */
489     /* Create the subpicture unit */
490     p_spu = decoder_NewSubpicture( p_dec, NULL );
491     if( !p_spu )
492     {
493         msg_Warn( p_dec, "can't get spu buffer" );
494         return NULL;
495     }
496
497     memset( &fmt, 0, sizeof(video_format_t) );
498     fmt.i_chroma = b_text ? VLC_CODEC_TEXT : VLC_CODEC_RGBA;
499     fmt.i_sar_num = 0;
500     fmt.i_sar_den = 1;
501     if( b_text )
502     {
503         fmt.i_bits_per_pixel = 0;
504     }
505     else
506     {
507         fmt.i_width = fmt.i_visible_width = i_columns * 12;
508         fmt.i_height = fmt.i_visible_height = i_rows * 10;
509         fmt.i_bits_per_pixel = 32;
510     }
511     fmt.i_x_offset = fmt.i_y_offset = 0;
512
513     p_spu->p_region = subpicture_region_New( &fmt );
514     if( p_spu->p_region == NULL )
515     {
516         msg_Err( p_dec, "cannot allocate SPU region" );
517         decoder_DeleteSubpicture( p_dec, p_spu );
518         return NULL;
519     }
520
521     p_spu->p_region->i_x = 0;
522     p_spu->p_region->i_y = 0;
523
524     p_spu->i_start = i_pts;
525     p_spu->i_stop = i_pts + 10000000;
526     p_spu->b_ephemer = true;
527     p_spu->b_absolute = b_text ? false : true;
528     p_spu->p_region->i_align = i_align;
529
530     if( !b_text )
531     {
532         p_spu->i_original_picture_width = fmt.i_width;
533         p_spu->i_original_picture_height = fmt.i_height;
534     }
535
536     /* */
537     *p_fmt = fmt;
538     return p_spu;
539 }
540
541 static void EventHandler( vbi_event *ev, void *user_data )
542 {
543     decoder_t *p_dec        = (decoder_t *)user_data;
544     decoder_sys_t *p_sys    = p_dec->p_sys;
545
546     if( ev->type == VBI_EVENT_TTX_PAGE )
547     {
548 #ifdef ZVBI_DEBUG
549         msg_Info( p_dec, "Page %03x.%02x ",
550                     ev->ev.ttx_page.pgno,
551                     ev->ev.ttx_page.subno & 0xFF);
552 #endif
553         if( p_sys->i_last_page == vbi_bcd2dec( ev->ev.ttx_page.pgno ) )
554             p_sys->b_update = true;
555 #ifdef ZVBI_DEBUG
556         if( ev->ev.ttx_page.clock_update )
557             msg_Dbg( p_dec, "clock" );
558         if( ev->ev.ttx_page.header_update )
559             msg_Dbg( p_dec, "header" );
560 #endif
561     }
562     else if( ev->type == VBI_EVENT_CLOSE )
563         msg_Dbg( p_dec, "Close event" );
564     else if( ev->type == VBI_EVENT_CAPTION )
565         msg_Dbg( p_dec, "Caption line: %x", ev->ev.caption.pgno );
566     else if( ev->type == VBI_EVENT_NETWORK )
567     {
568         msg_Dbg( p_dec, "Network change");
569         vbi_network n = ev->ev.network;
570         msg_Dbg( p_dec, "Network id:%d name: %s, call: %s ", n.nuid, n.name, n.call );
571     }
572     else if( ev->type == VBI_EVENT_TRIGGER )
573         msg_Dbg( p_dec, "Trigger event" );
574     else if( ev->type == VBI_EVENT_ASPECT )
575         msg_Dbg( p_dec, "Aspect update" );
576     else if( ev->type == VBI_EVENT_PROG_INFO )
577         msg_Dbg( p_dec, "Program info received" );
578     else if( ev->type == VBI_EVENT_NETWORK_ID )
579         msg_Dbg( p_dec, "Network ID changed" );
580 }
581
582 static int get_first_visible_row( vbi_char *p_text, int rows, int columns)
583 {
584     for ( int i = 0; i < rows * columns; i++ )
585     {
586         if ( p_text[i].opacity != VBI_TRANSPARENT_SPACE )
587         {
588             return i / columns;
589         }
590     }
591
592     return rows;
593 }
594
595 static int get_last_visible_row( vbi_char *p_text, int rows, int columns)
596 {
597     for ( int i = rows * columns - 1; i >= 0; i-- )
598     {
599         if (p_text[i].opacity != VBI_TRANSPARENT_SPACE)
600         {
601             return ( i + columns - 1) / columns;
602         }
603     }
604
605     return 0;
606 }
607
608 static int OpaquePage( picture_t *p_src, const vbi_page *p_page,
609                        const video_format_t fmt, bool b_opaque, const int text_offset )
610 {
611     unsigned int    x, y;
612
613     assert( fmt.i_chroma == VLC_CODEC_RGBA );
614
615     /* Kludge since zvbi doesn't provide an option to specify opacity. */
616     for( y = 0; y < fmt.i_height; y++ )
617     {
618         for( x = 0; x < fmt.i_width; x++ )
619         {
620             const vbi_opacity opacity = p_page->text[ text_offset + y/10 * p_page->columns + x/12 ].opacity;
621             const int background = p_page->text[ text_offset + y/10 * p_page->columns + x/12 ].background;
622             uint32_t *p_pixel = (uint32_t*)&p_src->p->p_pixels[y * p_src->p->i_pitch + 4*x];
623
624             switch( opacity )
625             {
626             /* Show video instead of this character */
627             case VBI_TRANSPARENT_SPACE:
628                 *p_pixel = 0;
629                 break;
630             /* Display foreground and background color */
631             /* To make the boxed text "closed captioning" transparent
632              * change true to false.
633              */
634             case VBI_OPAQUE:
635             /* alpha blend video into background color */
636             case VBI_SEMI_TRANSPARENT:
637                 if( b_opaque )
638                     break;
639             /* Full text transparency. only foreground color is show */
640             case VBI_TRANSPARENT_FULL:
641                 if( (*p_pixel) == (0xff000000 | p_page->color_map[background] ) )
642                     *p_pixel = 0;
643                 break;
644             }
645         }
646     }
647     /* end of kludge */
648     return VLC_SUCCESS;
649 }
650
651 /* Callbacks */
652 static int RequestPage( vlc_object_t *p_this, char const *psz_cmd,
653                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
654 {
655     decoder_sys_t *p_sys = p_data;
656     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
657
658     vlc_mutex_lock( &p_sys->lock );
659     switch( newval.i_int )
660     {
661         case ZVBI_KEY_RED:
662             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[0].pgno );
663             p_sys->i_wanted_subpage = p_sys->nav_link[0].subno;
664             break;
665         case ZVBI_KEY_GREEN:
666             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[1].pgno );
667             p_sys->i_wanted_subpage = p_sys->nav_link[1].subno;
668             break;
669         case ZVBI_KEY_YELLOW:
670             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[2].pgno );
671             p_sys->i_wanted_subpage = p_sys->nav_link[2].subno;
672             break;
673         case ZVBI_KEY_BLUE:
674             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[3].pgno );
675             p_sys->i_wanted_subpage = p_sys->nav_link[3].subno;
676             break;
677         case ZVBI_KEY_INDEX:
678             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[5].pgno ); /* #4 is SKIPPED */
679             p_sys->i_wanted_subpage = p_sys->nav_link[5].subno;
680             break;
681     }
682     if( newval.i_int > 0 && newval.i_int < 999 )
683     {
684         p_sys->i_wanted_page = newval.i_int;
685         p_sys->i_wanted_subpage = VBI_ANY_SUBNO;
686     }
687     vlc_mutex_unlock( &p_sys->lock );
688
689     return VLC_SUCCESS;
690 }
691
692 static int Opaque( vlc_object_t *p_this, char const *psz_cmd,
693                    vlc_value_t oldval, vlc_value_t newval, void *p_data )
694 {
695     decoder_sys_t *p_sys = p_data;
696     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
697
698     vlc_mutex_lock( &p_sys->lock );
699     p_sys->b_opaque = newval.b_bool;
700     p_sys->b_update = true;
701     vlc_mutex_unlock( &p_sys->lock );
702
703     return VLC_SUCCESS;
704 }
705
706 static int Position( vlc_object_t *p_this, char const *psz_cmd,
707                      vlc_value_t oldval, vlc_value_t newval, void *p_data )
708 {
709     decoder_sys_t *p_sys = p_data;
710     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
711
712     vlc_mutex_lock( &p_sys->lock );
713     p_sys->i_align = newval.i_int;
714     vlc_mutex_unlock( &p_sys->lock );
715
716     return VLC_SUCCESS;
717 }
718
719 static int EventKey( vlc_object_t *p_this, char const *psz_cmd,
720                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
721 {
722     decoder_t *p_dec = p_data;
723     decoder_sys_t *p_sys = p_dec->p_sys;
724
725     VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval); VLC_UNUSED( p_this );
726
727     /* FIXME: Capture + and - key for subpage browsing */
728     if( newval.i_int == '-' || newval.i_int == '+' )
729     {
730         vlc_mutex_lock( &p_sys->lock );
731         if( p_sys->i_wanted_subpage == VBI_ANY_SUBNO && newval.i_int == '+' )
732             p_sys->i_wanted_subpage = vbi_dec2bcd(1);
733         else if ( newval.i_int == '+' )
734             p_sys->i_wanted_subpage = vbi_add_bcd( p_sys->i_wanted_subpage, 1);
735         else if( newval.i_int == '-')
736             p_sys->i_wanted_subpage = vbi_add_bcd( p_sys->i_wanted_subpage, 0xF9999999); /* BCD complement - 1 */
737
738         if ( !vbi_bcd_digits_greater( p_sys->i_wanted_subpage, 0x00 ) || vbi_bcd_digits_greater( p_sys->i_wanted_subpage, 0x99 ) )
739                 p_sys->i_wanted_subpage = VBI_ANY_SUBNO;
740         else
741             msg_Info( p_dec, "subpage: %d",
742                       vbi_bcd2dec( p_sys->i_wanted_subpage) );
743
744         p_sys->b_update = true;
745         vlc_mutex_unlock( &p_sys->lock );
746     }
747
748     /* Capture 0-9 for page selection */
749     if( newval.i_int < '0' || newval.i_int > '9' )
750         return VLC_SUCCESS;
751
752     vlc_mutex_lock( &p_sys->lock );
753     p_sys->i_key[0] = p_sys->i_key[1];
754     p_sys->i_key[1] = p_sys->i_key[2];
755     p_sys->i_key[2] = (int)(newval.i_int - '0');
756     msg_Info( p_dec, "page: %c%c%c", (char)(p_sys->i_key[0]+'0'),
757               (char)(p_sys->i_key[1]+'0'), (char)(p_sys->i_key[2]+'0') );
758
759     int i_new_page = 0;
760
761     if( p_sys->i_key[0] > 0 && p_sys->i_key[0] <= 8 &&
762         p_sys->i_key[1] >= 0 && p_sys->i_key[1] <= 9 &&
763         p_sys->i_key[2] >= 0 && p_sys->i_key[2] <= 9 )
764     {
765         i_new_page = p_sys->i_key[0]*100 + p_sys->i_key[1]*10 + p_sys->i_key[2];
766         p_sys->i_key[0] = p_sys->i_key[1] = p_sys->i_key[2] = '*' - '0';
767     }
768     vlc_mutex_unlock( &p_sys->lock );
769
770     if( i_new_page > 0 )
771         var_SetInteger( p_dec, "vbi-page", i_new_page );
772
773     return VLC_SUCCESS;
774 }