codecs: add support for ARIB subtitles
[vlc.git] / modules / codec / arib / aribsub.c
1 /*****************************************************************************
2  * aribsub.c : ARIB subtitles decoder
3  *****************************************************************************
4  * Copyright (C) 2012 Naohiro KORIYAMA
5  *
6  * Authors:  Naohiro KORIYAMA <nkoriyama@gmail.com>
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #include <vlc_common.h>
28 #include <vlc_plugin.h>
29 #include <vlc_codec.h>
30
31 #ifdef HAVE_ARIBB24
32  #include "substext.h"
33  #include <aribb24/parser.h>
34  #include <aribb24/decoder.h>
35 #endif
36
37 //#define DEBUG_ARIBSUB 1
38
39 /*****************************************************************************
40  * Module descriptor.
41  *****************************************************************************/
42 static int  Open( vlc_object_t * );
43 static void Close( vlc_object_t * );
44 static subpicture_t *Decode( decoder_t *, block_t ** );
45
46 #define IGNORE_RUBY_TEXT N_("Ignore ruby(furigana)")
47 #define IGNORE_RUBY_LONGTEXT N_("Ignore ruby(furigana) in the subtitle.")
48 #define USE_CORETEXT_TEXT N_("Use Core Text renderer")
49 #define USE_CORETEXT_LONGTEXT N_("Use Core Text renderer in the subtitle.")
50
51 vlc_module_begin ()
52 #   define ARIBSUB_CFG_PREFIX "aribsub-"
53     set_description( N_("ARIB subtitles decoder") )
54     set_shortname( N_("ARIB subtitles") )
55     set_capability( "decoder", 50 )
56     set_category( CAT_INPUT )
57     set_subcategory( SUBCAT_INPUT_SCODEC )
58     set_callbacks( Open, Close )
59
60     add_bool( ARIBSUB_CFG_PREFIX "ignore-ruby", false, IGNORE_RUBY_TEXT, IGNORE_RUBY_LONGTEXT, true )
61     add_bool( ARIBSUB_CFG_PREFIX "use-coretext", false, USE_CORETEXT_TEXT, USE_CORETEXT_LONGTEXT, true )
62 vlc_module_end ()
63
64
65 /****************************************************************************
66  * Local structures
67  ****************************************************************************/
68
69 struct decoder_sys_t
70 {
71     bool              b_a_profile;
72     bool              b_ignore_ruby;
73     bool              b_use_coretext;
74     bool              b_ignore_position_adjustment;
75
76     arib_instance_t  *p_arib_instance;
77     char             *psz_arib_base_dir;
78 };
79
80 /*****************************************************************************
81  * Local prototypes
82  *****************************************************************************/
83 static subpicture_t *render( decoder_t *, arib_parser_t *,
84                              arib_decoder_t *, block_t * );
85
86 static char* get_arib_base_dir( void );
87 static void messages_callback_handler( void *, const char *psz_message );
88
89 /*****************************************************************************
90  * Open: probe the decoder and return score
91  *****************************************************************************
92  * Tries to launch a decoder and return score so that the interface is able
93  * to choose.
94  *****************************************************************************/
95 static int Open( vlc_object_t *p_this )
96 {
97     decoder_t     *p_dec = (decoder_t *) p_this;
98     decoder_sys_t *p_sys;
99
100     if( p_dec->fmt_in.i_codec != VLC_CODEC_ARIB_A &&
101         p_dec->fmt_in.i_codec != VLC_CODEC_ARIB_C )
102     {
103         return VLC_EGENERIC;
104     }
105
106     p_sys = (decoder_sys_t*) calloc( 1, sizeof(decoder_sys_t) );
107     if( p_sys == NULL )
108     {
109         return VLC_ENOMEM;
110     }
111
112     p_sys->p_arib_instance = arib_instance_new( (void *) p_this );
113     if ( !p_sys->p_arib_instance )
114     {
115         free( p_sys );
116         return VLC_EGENERIC;
117     }
118
119     p_dec->p_sys = p_sys;
120     p_dec->pf_decode_sub = Decode;
121     p_dec->fmt_out.i_cat = SPU_ES;
122     p_dec->fmt_out.i_codec = 0;
123
124     p_sys->b_a_profile = ( p_dec->fmt_in.i_codec == VLC_CODEC_ARIB_A );
125
126     p_sys->b_ignore_ruby =
127         var_InheritBool( p_this, ARIBSUB_CFG_PREFIX "ignore-ruby" );
128     p_sys->b_use_coretext =
129         var_InheritBool( p_this, ARIBSUB_CFG_PREFIX "use-coretext" );
130     p_sys->b_ignore_position_adjustment = p_sys->b_use_coretext;
131     p_sys->p_arib_instance->b_use_private_conv = p_sys->b_use_coretext;
132     p_sys->p_arib_instance->b_replace_ellipsis = p_sys->b_use_coretext;
133
134     char *psz_basedir = get_arib_base_dir();
135     arib_set_base_path( p_sys->p_arib_instance, psz_basedir );
136     free( psz_basedir );
137
138     arib_register_messages_callback( p_sys->p_arib_instance,
139                                      messages_callback_handler );
140
141     return VLC_SUCCESS;
142 }
143
144 /*****************************************************************************
145  * Close:
146  *****************************************************************************/
147 static void Close( vlc_object_t *p_this )
148 {
149     decoder_t     *p_dec = (decoder_t*) p_this;
150     decoder_sys_t *p_sys = p_dec->p_sys;
151
152     arib_instance_destroy( p_sys->p_arib_instance );
153     free( p_sys->psz_arib_base_dir );
154
155     var_Destroy( p_this, ARIBSUB_CFG_PREFIX "ignore-ruby" );
156     var_Destroy( p_this, ARIBSUB_CFG_PREFIX "use-coretext" );
157
158     free( p_sys );
159 }
160
161 /*****************************************************************************
162  * Decode:
163  *****************************************************************************/
164 static subpicture_t *Decode( decoder_t *p_dec, block_t **pp_block )
165 {
166     decoder_sys_t *p_sys = p_dec->p_sys;
167     block_t       *p_block;
168     subpicture_t  *p_spu = NULL;
169
170     if( ( pp_block == NULL ) || ( *pp_block == NULL ) )
171     {
172         return NULL;
173     }
174     p_block = *pp_block;
175
176     arib_parser_t *p_parser = arib_get_parser( p_sys->p_arib_instance );
177     arib_decoder_t *p_decoder = arib_get_decoder( p_sys->p_arib_instance );
178     if ( p_parser && p_decoder )
179     {
180         arib_parse_pes( p_parser, p_block->p_buffer, p_block->i_buffer );
181         p_spu = render( p_dec, p_parser, p_decoder, p_block );
182     }
183
184     block_Release( p_block );
185     *pp_block = NULL;
186
187     return p_spu;
188 }
189
190 /* following functions are local */
191
192 static void messages_callback_handler( void *p_opaque, const char *psz_message )
193 {
194     decoder_t *p_dec = ( decoder_t * ) p_opaque;
195     msg_Dbg( p_dec, "%s", psz_message );
196 }
197
198 static char* get_arib_base_dir()
199 {
200     char *psz_data_dir = config_GetUserDir( VLC_DATA_DIR );
201     if( psz_data_dir == NULL )
202     {
203         return NULL;
204     }
205
206     char *psz_arib_base_dir;
207     if( asprintf( &psz_arib_base_dir, "%s"DIR_SEP"arib", psz_data_dir ) < 0 )
208     {
209         psz_arib_base_dir = NULL;
210     }
211     free( psz_data_dir );
212
213     return psz_arib_base_dir;
214 }
215
216 static subpicture_t *render( decoder_t *p_dec, arib_parser_t *p_parser,
217                              arib_decoder_t *p_arib_decoder, block_t *p_block )
218 {
219     decoder_sys_t *p_sys = p_dec->p_sys;
220     subpicture_t *p_spu = NULL;
221     char *psz_subtitle = NULL;
222
223     size_t i_data_size;
224     const unsigned char *psz_data = arib_parser_get_data( p_parser, &i_data_size );
225     if( !psz_data || !i_data_size )
226         return NULL;
227
228     size_t i_subtitle_size = i_data_size * 4;
229     psz_subtitle = (char*) calloc( i_subtitle_size + 1, sizeof(*psz_subtitle) );
230     if( psz_subtitle == NULL )
231     {
232         return NULL;
233     }
234     if( p_sys->b_a_profile )
235         arib_initialize_decoder_a_profile( p_arib_decoder );
236     else
237         arib_initialize_decoder_c_profile( p_arib_decoder );
238
239     i_subtitle_size = arib_decode_buffer( p_arib_decoder,
240                                           psz_data,
241                                           i_data_size,
242                                           psz_subtitle,
243                                           i_subtitle_size );
244 #ifdef DEBUG_ARIBSUB
245     msg_Dbg( p_dec, "psz_subtitle [%s]", psz_subtitle );
246     unsigned const char* start = psz_data;
247     unsigned const char* end = psz_data + i_data_size;
248     char* psz_subtitle_data_hex = (char*) calloc(
249             i_data_size * 3 + 1, sizeof(char) );
250     char* psz_subtitle_data_hex_idx = psz_subtitle_data_hex;
251     while( start < end )
252     {
253         sprintf(psz_subtitle_data_hex_idx, "%02x ", *start++);
254         psz_subtitle_data_hex_idx += 3;
255     }
256     msg_Dbg( p_dec, "psz_subtitle_data [%s]", psz_subtitle_data_hex);
257     free( psz_subtitle_data_hex );
258 #endif
259
260     p_spu = decoder_NewSubpictureText( p_dec );
261     if( p_spu == NULL )
262     {
263         goto decoder_NewSubpictureText_failed;
264     }
265
266     p_spu->i_start = p_block->i_pts;
267     p_spu->i_stop = p_block->i_pts + arib_decoder_get_time( p_arib_decoder );
268     p_spu->b_ephemer  = (p_spu->i_start == p_spu->i_stop);
269     p_spu->b_absolute = true;
270
271     subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
272
273     arib_text_region_t *p_region = p_spu_sys->p_region =
274         (arib_text_region_t*) calloc( 1, sizeof(arib_text_region_t) );
275     if( p_region == NULL )
276     {
277         goto malloc_failed;
278     }
279     for( const arib_buf_region_t *p_buf_region = arib_decoder_get_regions( p_arib_decoder );
280          p_buf_region; p_buf_region = p_buf_region->p_next )
281     {
282         if( p_sys->b_ignore_ruby && p_buf_region->i_fontheight == 18 )
283         {
284             continue;
285         }
286
287         int i_size = p_buf_region->p_end - p_buf_region->p_start;
288         char *psz_text = (char*) calloc( i_size + 1, sizeof(char) );
289         if( psz_text == NULL )
290         {
291             goto malloc_failed;
292         }
293         strncpy(psz_text, p_buf_region->p_start, i_size);
294         psz_text[i_size] = '\0';
295 #ifdef DEBUG_ARIBSUB
296         msg_Dbg( p_dec, "psz_text [%s]", psz_text );
297 #endif
298
299         p_region->psz_text = strdup( psz_text );
300         free( psz_text );
301         p_region->psz_html = NULL;
302         p_region->psz_fontname = NULL;
303         p_region->i_font_color = p_buf_region->i_foreground_color;
304         p_region->i_planewidth = p_buf_region->i_planewidth;
305         p_region->i_planeheight = p_buf_region->i_planeheight;
306         p_region->i_fontwidth = p_buf_region->i_fontwidth;
307         p_region->i_fontheight = p_buf_region->i_fontheight;
308         p_region->i_verint = p_buf_region->i_verint;
309         p_region->i_horint = p_buf_region->i_horint;
310         p_region->i_charleft = p_buf_region->i_charleft;
311         p_region->i_charbottom = p_buf_region->i_charbottom;
312         p_region->i_charleft_adj = 0;
313         p_region->i_charbottom_adj = 0;
314         if( !p_sys->b_ignore_position_adjustment )
315         {
316             p_region->i_charleft_adj = p_buf_region->i_horadj;
317             p_region->i_charbottom_adj = p_buf_region->i_veradj;
318         }
319         p_region->p_next = NULL;
320         if( p_buf_region->p_next != NULL )
321         {
322             p_region = p_region->p_next =
323                 (arib_text_region_t*) calloc( 1, sizeof(arib_text_region_t) );
324             if( p_region == NULL )
325             {
326                 goto malloc_failed;
327             }
328         }
329     }
330
331 decoder_NewSubpictureText_failed:
332 malloc_failed:
333     arib_finalize_decoder( p_arib_decoder );
334     free( psz_subtitle );
335
336     return p_spu;
337 }