player: osd: fix zoom
[vlc.git] / modules / codec / stl.c
blob21a206ab0fd908fcb853cd2178daa7ca0fb352a0
1 /*****************************************************************************
2 * stl.c: EBU STL decoder
3 *****************************************************************************
4 * Copyright (C) 2010 Laurent Aimar
5 * $Id$
7 * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
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.
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.
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 *****************************************************************************/
24 /*****************************************************************************
25 * Preamble
26 *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 #include <assert.h>
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_codec.h>
35 #include <vlc_charset.h>
37 #include "substext.h" /* required for font scaling / updater */
39 /*****************************************************************************
40 * Module descriptor
41 *****************************************************************************/
42 static int Open (vlc_object_t *);
43 static void Close(vlc_object_t *);
45 vlc_module_begin()
46 set_description(N_("EBU STL subtitles decoder"))
47 set_category(CAT_INPUT)
48 set_subcategory(SUBCAT_INPUT_SCODEC)
49 set_capability("spu decoder", 10)
50 set_callbacks(Open, Close)
51 vlc_module_end()
53 /*****************************************************************************
54 * Local definitions/prototypes
55 *****************************************************************************/
56 #define GSI_BLOCK_SIZE 1024
58 #define STL_GROUPS_MAX 255
60 #define STL_TEXTFIELD_SIZE 112
61 #define STL_TTI_HEADER_SIZE 16
62 #define STL_TTI_SIZE (STL_TTI_HEADER_SIZE + STL_TEXTFIELD_SIZE)
64 #define STL_TF_TELETEXT_FIRST 0x00
65 #define STL_TF_TELETEXT_LAST 0x1f
66 #define STL_TF_CHARCODE1_FIRST 0x20
67 #define STL_TF_CHARCODE1_LAST 0x7f
68 #define STL_TF_ITALICS_ON 0x80
69 #define STL_TF_ITALICS_OFF 0x81
70 #define STL_TF_UNDERLINE_ON 0x82
71 #define STL_TF_UNDERLINE_OFF 0x83
72 #define STL_TF_BOXING_ON 0x84
73 #define STL_TF_BOXING_OFF 0x85
74 #define STL_TF_LINEBREAK 0x8a
75 #define STL_TF_END_FILL 0x8f
76 #define STL_TF_CHARCODE2_FIRST 0xa1
78 typedef enum {
79 CCT_ISO_6937_2 = 0x3030, CCT_BEGIN = CCT_ISO_6937_2,
80 CCT_ISO_8859_5 = 0x3031,
81 CCT_ISO_8859_6 = 0x3032,
82 CCT_ISO_8859_7 = 0x3033,
83 CCT_ISO_8859_8 = 0x3034, CCT_END = CCT_ISO_8859_8
84 } cct_number_value_t;
86 typedef struct
88 uint8_t i_accumulating;
89 uint8_t i_justify;
90 vlc_tick_t i_start;
91 vlc_tick_t i_end;
92 text_style_t *p_style;
93 text_segment_t *p_segment;
94 text_segment_t **pp_segment_last;
95 } stl_sg_t;
97 typedef struct {
98 cct_number_value_t value;
99 const char *str;
100 } cct_number_t;
102 typedef struct
104 stl_sg_t groups[STL_GROUPS_MAX + 1];
105 cct_number_value_t cct;
106 uint8_t i_fps;
107 } decoder_sys_t;
109 static cct_number_t cct_nums[] = { {CCT_ISO_6937_2, "ISO_6937-2"},
110 {CCT_ISO_8859_5, "ISO_8859-5"},
111 {CCT_ISO_8859_6, "ISO_8859-6"},
112 {CCT_ISO_8859_7, "ISO_8859-7"},
113 {CCT_ISO_8859_8, "ISO_8859-8"} };
115 static text_style_t * CreateGroupStyle()
117 text_style_t *p_style = text_style_Create(STYLE_NO_DEFAULTS);
118 if(p_style)
120 p_style->i_features = STYLE_HAS_FLAGS|STYLE_HAS_BACKGROUND_ALPHA|STYLE_HAS_BACKGROUND_COLOR;
121 /* Teletext needs default background to black */
122 p_style->i_background_alpha = STYLE_ALPHA_OPAQUE;
123 p_style->i_background_color = 0x000000;
124 p_style->i_font_size = 0;
125 p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE;
127 return p_style;
130 static void TextBufferFlush(stl_sg_t *p_group, uint8_t *p_buf, uint8_t *pi_buf,
131 const char *psz_charset)
133 if(*pi_buf == 0)
134 return;
136 char *psz_utf8 = FromCharset(psz_charset, p_buf, *pi_buf);
137 if(psz_utf8)
139 *p_group->pp_segment_last = text_segment_New(psz_utf8);
140 if(*p_group->pp_segment_last)
142 if(p_group->p_style)
143 (*p_group->pp_segment_last)->style = text_style_Duplicate(p_group->p_style);
144 p_group->pp_segment_last = &((*p_group->pp_segment_last)->p_next);
146 free(psz_utf8);
149 *pi_buf = 0;
152 static void GroupParseTeletext(stl_sg_t *p_group, uint8_t code)
154 if(p_group->p_style == NULL &&
155 !(p_group->p_style = CreateGroupStyle()))
156 return;
158 /* See ETS 300 706 Table 26 as EBU 3264 does only name values
159 and does not explain at all */
161 static const uint32_t colors[] =
163 0x000000,
164 0xFF0000,
165 0x00FF00,
166 0xFFFF00,
167 0x0000FF,
168 0xFF00FF,
169 0x00FFFF,
170 0xFFFFFF,
173 /* Teletext data received, so we need to enable background */
174 p_group->p_style->i_style_flags |= STYLE_BACKGROUND;
176 switch(code)
178 case 0x0c:
179 p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE;
180 p_group->p_style->i_style_flags &= ~(STYLE_DOUBLEWIDTH|STYLE_HALFWIDTH);
181 break;
183 case 0x0d: /* double height */
184 p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE * 2;
185 p_group->p_style->i_style_flags &= ~STYLE_DOUBLEWIDTH;
186 p_group->p_style->i_style_flags |= STYLE_HALFWIDTH;
187 break;
189 case 0x0e: /* double width */
190 p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE;
191 p_group->p_style->i_style_flags &= ~STYLE_HALFWIDTH;
192 p_group->p_style->i_style_flags |= STYLE_DOUBLEWIDTH;
193 break;
195 case 0x0f: /* double size */
196 p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE * 2;
197 p_group->p_style->i_style_flags &= ~(STYLE_DOUBLEWIDTH|STYLE_HALFWIDTH);
198 break;
200 case 0x1d:
201 p_group->p_style->i_background_color = p_group->p_style->i_font_color;
202 p_group->p_style->i_features &= ~STYLE_HAS_FONT_COLOR;
203 break;
205 case 0x1c:
206 p_group->p_style->i_background_color = colors[0];
207 break;
209 default:
210 if(code < 8)
212 p_group->p_style->i_font_color = colors[code];
213 p_group->p_style->i_features |= STYLE_HAS_FONT_COLOR;
216 /* Need to handle Mosaic ? Really ? */
217 break;
222 static void GroupApplyStyle(stl_sg_t *p_group, uint8_t code)
224 if(p_group->p_style == NULL &&
225 !(p_group->p_style = CreateGroupStyle()))
226 return;
228 switch(code)
230 case STL_TF_ITALICS_ON:
231 p_group->p_style->i_style_flags |= STYLE_ITALIC;
232 break;
233 case STL_TF_ITALICS_OFF:
234 p_group->p_style->i_style_flags &= ~STYLE_ITALIC;
235 break;
236 case STL_TF_UNDERLINE_ON:
237 p_group->p_style->i_style_flags |= STYLE_UNDERLINE;
238 break;
239 case STL_TF_UNDERLINE_OFF:
240 p_group->p_style->i_style_flags &= ~STYLE_UNDERLINE;
241 break;
242 case STL_TF_BOXING_ON:
243 case STL_TF_BOXING_OFF:
244 default:
245 break;
249 static vlc_tick_t ParseTimeCode(const uint8_t *data, double fps)
251 return vlc_tick_from_sec( data[0] * 3600 +
252 data[1] * 60 +
253 data[2] * 1 +
254 data[3] / fps);
257 static void ClearTeletextStyles(stl_sg_t *p_group)
259 if(p_group->p_style)
261 p_group->p_style->i_features &= ~STYLE_HAS_FONT_COLOR;
262 p_group->p_style->i_background_color = 0x000000;
263 p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE;
264 p_group->p_style->i_style_flags &= ~(STYLE_DOUBLEWIDTH|STYLE_HALFWIDTH);
268 /* Returns true if group is we need to output group */
269 static bool ParseTTI(stl_sg_t *p_group, const uint8_t *p_data, const char *psz_charset, double fps)
271 uint8_t p_buffer[STL_TEXTFIELD_SIZE];
272 uint8_t i_buffer = 0;
274 /* Header */
275 uint8_t ebn = p_data[3];
276 if(ebn > 0xef && ebn != 0xff)
277 return false;
279 if(p_data[15] != 0x00) /* comment flag */
280 return false;
282 if(p_data[14] > 0x00)
283 p_group->i_justify = p_data[14];
285 /* Accumulating started or continuing.
286 * We must not flush current segments on output and continue on next block */
287 p_group->i_accumulating = (p_data[4] == 0x01 || p_data[4] == 0x02);
289 p_group->i_start = ParseTimeCode( &p_data[5], fps );
290 p_group->i_end = ParseTimeCode( &p_data[9], fps );
292 /* Text Field */
293 for (size_t i = STL_TTI_HEADER_SIZE; i < STL_TTI_SIZE; i++)
295 const uint8_t code = p_data[i];
296 switch(code)
298 case STL_TF_LINEBREAK:
299 p_buffer[i_buffer++] = '\n';
300 TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
301 /* Clear teletext styles on each new row */
302 ClearTeletextStyles(p_group);
303 break;
305 case STL_TF_END_FILL:
306 TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
307 ClearTeletextStyles(p_group);
308 return true;
310 default:
311 if(code <= STL_TF_TELETEXT_LAST)
313 TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
314 GroupParseTeletext(p_group, code);
316 else if((code >= STL_TF_CHARCODE1_FIRST && code <= STL_TF_CHARCODE1_LAST) ||
317 code >= STL_TF_CHARCODE2_FIRST)
319 p_buffer[i_buffer++] = code;
321 else if(code >= STL_TF_ITALICS_ON && code <= STL_TF_BOXING_OFF)
323 TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
324 GroupApplyStyle(p_group, code);
326 break;
330 TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
332 return false;
335 static void FillSubpictureUpdater(stl_sg_t *p_group, subtext_updater_sys_t *p_spu_sys)
337 if(p_group->i_accumulating)
339 p_spu_sys->region.p_segments = text_segment_Copy(p_group->p_segment);
341 else
343 p_spu_sys->region.p_segments = p_group->p_segment;
344 p_group->p_segment = NULL;
345 p_group->pp_segment_last = &p_group->p_segment;
348 p_spu_sys->region.align = SUBPICTURE_ALIGN_BOTTOM;
349 if(p_group->i_justify == 0x01)
350 p_spu_sys->region.inner_align = SUBPICTURE_ALIGN_LEFT;
351 else if(p_group->i_justify == 0x03)
352 p_spu_sys->region.inner_align = SUBPICTURE_ALIGN_RIGHT;
355 static void ResetGroups(decoder_sys_t *p_sys)
357 for(size_t i=0; i<=STL_GROUPS_MAX; i++)
359 stl_sg_t *p_group = &p_sys->groups[i];
360 if(p_group->p_segment)
362 text_segment_ChainDelete(p_group->p_segment);
363 p_group->p_segment = NULL;
364 p_group->pp_segment_last = &p_group->p_segment;
367 if(p_group->p_style)
369 text_style_Delete(p_group->p_style);
370 p_group->p_style = NULL;
373 p_group->i_accumulating = false;
374 p_group->i_end = VLC_TICK_INVALID;
375 p_group->i_start = VLC_TICK_INVALID;
376 p_group->i_justify = 0;
380 static int Decode(decoder_t *p_dec, block_t *p_block)
382 if (p_block == NULL) /* No Drain */
383 return VLCDEC_SUCCESS;
385 decoder_sys_t *p_sys = p_dec->p_sys;
387 if(p_block->i_buffer < STL_TTI_SIZE)
388 p_block->i_flags |= BLOCK_FLAG_CORRUPTED;
390 if(p_block->i_flags & (BLOCK_FLAG_CORRUPTED|BLOCK_FLAG_DISCONTINUITY))
392 ResetGroups(p_dec->p_sys);
394 if(p_block->i_flags & BLOCK_FLAG_CORRUPTED)
396 block_Release(p_block);
397 return VLCDEC_SUCCESS;
401 const char *psz_charset = cct_nums[p_sys->cct - CCT_BEGIN].str;
402 for (size_t i = 0; i < p_block->i_buffer / STL_TTI_SIZE; i++)
404 stl_sg_t *p_group = &p_sys->groups[p_block->p_buffer[0]];
405 if(ParseTTI(p_group, &p_block->p_buffer[i * STL_TTI_SIZE], psz_charset, p_sys->i_fps) &&
406 p_group->p_segment != NULL )
408 /* output */
409 subpicture_t *p_sub = decoder_NewSubpictureText(p_dec);
410 if( p_sub )
412 FillSubpictureUpdater(p_group, p_sub->updater.p_sys );
414 p_sub->b_absolute = false;
416 if(p_group->i_end != VLC_TICK_INVALID && p_group->i_start >= p_block->i_dts)
418 p_sub->i_start = VLC_TICK_0 + p_group->i_start;
419 p_sub->i_stop = VLC_TICK_0 + p_group->i_end;
421 else
423 p_sub->i_start = p_block->i_pts;
424 p_sub->i_stop = p_block->i_pts + p_block->i_length;
425 p_sub->b_ephemer = (p_block->i_length == VLC_TICK_INVALID);
427 decoder_QueueSub(p_dec, p_sub);
432 ResetGroups(p_sys);
434 block_Release(p_block);
435 return VLCDEC_SUCCESS;
438 static int ParseGSI(const decoder_t *dec, decoder_sys_t *p_sys)
440 uint8_t *header = dec->fmt_in.p_extra;
441 if (!header) {
442 msg_Err(dec, "NULL EBU header (GSI block)\n");
443 return VLC_EGENERIC;
446 if (GSI_BLOCK_SIZE != dec->fmt_in.i_extra) {
447 msg_Err(dec, "EBU header is not in expected size (%d)\n", dec->fmt_in.i_extra);
448 return VLC_EGENERIC;
451 char dfc_fps_str[] = { header[6], header[7], '\0' };
452 int fps = strtol(dfc_fps_str, NULL, 10);
453 if (1 > fps || 60 < fps) {
454 msg_Warn(dec, "EBU header contains unsupported DFC fps ('%s'); falling back to 25\n", dfc_fps_str);
455 fps = 25;
458 int cct = (header[12] << 8) | header[13];
459 if (CCT_BEGIN > cct || CCT_END < cct) {
460 msg_Err(dec, "EBU header contains illegal CCT (0x%x)\n", cct);
461 return VLC_EGENERIC;
464 msg_Dbg(dec, "DFC fps=%d, CCT=0x%x", fps, cct);
465 p_sys->i_fps = fps;
466 p_sys->cct = cct;
468 return VLC_SUCCESS;
471 static int Open(vlc_object_t *object)
473 decoder_t *dec = (decoder_t*)object;
475 if (dec->fmt_in.i_codec != VLC_CODEC_EBU_STL)
476 return VLC_EGENERIC;
478 decoder_sys_t *sys = calloc(1, sizeof(*sys));
479 if (!sys)
480 return VLC_ENOMEM;
482 int rc = ParseGSI(dec, sys);
483 if (VLC_SUCCESS != rc)
484 return rc;
486 for(size_t i=0; i<=STL_GROUPS_MAX; i++)
487 sys->groups[i].pp_segment_last = &sys->groups[i].p_segment;
489 dec->p_sys = sys;
490 dec->pf_decode = Decode;
491 dec->fmt_out.i_codec = 0;
492 return VLC_SUCCESS;
495 static void Close(vlc_object_t *object)
497 decoder_t *dec = (decoder_t*)object;
498 decoder_sys_t *p_sys = dec->p_sys;
500 ResetGroups(p_sys);
501 free(p_sys);