1 /*****************************************************************************
2  * stl.c: EBU STL decoder
3  *****************************************************************************
4  * Copyright (C) 2010 Laurent Aimar
5  * $Id: 9c6ddc9663cb040bf563c4af784152fc4ffefd92 $
6  *
7  * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
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 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 #include <assert.h>
31 
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_codec.h>
35 #include <vlc_memory.h>
36 #include <vlc_charset.h>
37 
38 #include "substext.h" /* required for font scaling / updater */
39 
40 /*****************************************************************************
41  * Module descriptor
42  *****************************************************************************/
43 static int  Open (vlc_object_t *);
44 static void Close(vlc_object_t *);
45 
46 vlc_module_begin()
47     set_description(N_("EBU STL subtitles decoder"))
48     set_category(CAT_INPUT)
49     set_subcategory(SUBCAT_INPUT_SCODEC)
50     set_capability("spu decoder", 10)
51     set_callbacks(Open, Close)
52 vlc_module_end()
53 
54 /*****************************************************************************
55  * Local definitions/prototypes
56  *****************************************************************************/
57 #define GSI_BLOCK_SIZE 1024
58 
59 #define STL_GROUPS_MAX 255
60 
61 #define STL_TEXTFIELD_SIZE     112
62 #define STL_TTI_HEADER_SIZE    16
63 #define STL_TTI_SIZE           (STL_TTI_HEADER_SIZE + STL_TEXTFIELD_SIZE)
64 
65 #define STL_TF_TELETEXT_FIRST     0x00
66 #define STL_TF_TELETEXT_LAST      0x1f
67 #define STL_TF_CHARCODE1_FIRST    0x20
68 #define STL_TF_CHARCODE1_LAST     0x7f
69 #define STL_TF_ITALICS_ON         0x80
70 #define STL_TF_ITALICS_OFF        0x81
71 #define STL_TF_UNDERLINE_ON       0x82
72 #define STL_TF_UNDERLINE_OFF      0x83
73 #define STL_TF_BOXING_ON          0x84
74 #define STL_TF_BOXING_OFF         0x85
75 #define STL_TF_LINEBREAK          0x8a
76 #define STL_TF_END_FILL           0x8f
77 #define STL_TF_CHARCODE2_FIRST    0xa1
78 
79 typedef enum {
80     CCT_ISO_6937_2 = 0x3030, CCT_BEGIN = CCT_ISO_6937_2,
81     CCT_ISO_8859_5 = 0x3031,
82     CCT_ISO_8859_6 = 0x3032,
83     CCT_ISO_8859_7 = 0x3033,
84     CCT_ISO_8859_8 = 0x3034, CCT_END = CCT_ISO_8859_8
85 } cct_number_value_t;
86 
87 typedef struct
88 {
89     uint8_t i_accumulating;
90     uint8_t i_justify;
91     int64_t i_start;
92     int64_t i_end;
93     text_style_t *p_style;
94     text_segment_t *p_segment;
95     text_segment_t **pp_segment_last;
96 } stl_sg_t;
97 
98 typedef struct {
99     cct_number_value_t value;
100     const char *str;
101 } cct_number_t;
102 
103 struct decoder_sys_t {
104     stl_sg_t groups[STL_GROUPS_MAX + 1];
105     cct_number_value_t cct;
106     uint8_t i_fps;
107 };
108 
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"} };
114 
CreateGroupStyle()115 static text_style_t * CreateGroupStyle()
116 {
117     text_style_t *p_style = text_style_Create(STYLE_NO_DEFAULTS);
118     if(p_style)
119     {
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;
126     }
127     return p_style;
128 }
129 
TextBufferFlush(stl_sg_t * p_group,uint8_t * p_buf,uint8_t * pi_buf,const char * psz_charset)130 static void TextBufferFlush(stl_sg_t *p_group, uint8_t *p_buf, uint8_t *pi_buf,
131                             const char *psz_charset)
132 {
133     if(*pi_buf == 0)
134         return;
135 
136     char *psz_utf8 = FromCharset(psz_charset, p_buf, *pi_buf);
137     if(psz_utf8)
138     {
139         *p_group->pp_segment_last = text_segment_New(psz_utf8);
140         if(*p_group->pp_segment_last)
141         {
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);
145         }
146         free(psz_utf8);
147     }
148 
149     *pi_buf = 0;
150 }
151 
GroupParseTeletext(stl_sg_t * p_group,uint8_t code)152 static void GroupParseTeletext(stl_sg_t *p_group, uint8_t code)
153 {
154     if(p_group->p_style == NULL &&
155       !(p_group->p_style = CreateGroupStyle()))
156         return;
157 
158     /* See ETS 300 706 Table 26 as EBU 3264 does only name values
159        and does not explain at all */
160 
161     static const uint32_t colors[] =
162     {
163         0x000000,
164         0xFF0000,
165         0x00FF00,
166         0xFFFF00,
167         0x0000FF,
168         0xFF00FF,
169         0x00FFFF,
170         0xFFFFFF,
171     };
172 
173     /* Teletext data received, so we need to enable background */
174     p_group->p_style->i_style_flags |= STYLE_BACKGROUND;
175 
176     switch(code)
177     {
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;
182 
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;
188 
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;
194 
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;
199 
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;
204 
205         case 0x1c:
206             p_group->p_style->i_background_color = colors[0];
207             break;
208 
209         default:
210             if(code < 8)
211             {
212                 p_group->p_style->i_font_color = colors[code];
213                 p_group->p_style->i_features |= STYLE_HAS_FONT_COLOR;
214             }
215 
216             /* Need to handle Mosaic ? Really ? */
217             break;
218     }
219 
220 }
221 
GroupApplyStyle(stl_sg_t * p_group,uint8_t code)222 static void GroupApplyStyle(stl_sg_t *p_group, uint8_t code)
223 {
224     if(p_group->p_style == NULL &&
225       !(p_group->p_style = CreateGroupStyle()))
226         return;
227 
228     switch(code)
229     {
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;
246     }
247 }
248 
ParseTimeCode(const uint8_t * data,double fps)249 static int64_t ParseTimeCode(const uint8_t *data, double fps)
250 {
251     return INT64_C(1000000) * (data[0] * 3600 +
252                                data[1] *   60 +
253                                data[2] *    1 +
254                                data[3] /  fps);
255 }
256 
ClearTeletextStyles(stl_sg_t * p_group)257 static void ClearTeletextStyles(stl_sg_t *p_group)
258 {
259     if(p_group->p_style)
260     {
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);
265     }
266 }
267 
268 /* Returns true if group is we need to output group */
ParseTTI(stl_sg_t * p_group,const uint8_t * p_data,const char * psz_charset,double fps)269 static bool ParseTTI(stl_sg_t *p_group, const uint8_t *p_data, const char *psz_charset, double fps)
270 {
271     uint8_t p_buffer[STL_TEXTFIELD_SIZE];
272     uint8_t i_buffer = 0;
273 
274     /* Header */
275     uint8_t ebn = p_data[3];
276     if(ebn > 0xef && ebn != 0xff)
277         return false;
278 
279     if(p_data[15] != 0x00) /* comment flag */
280         return false;
281 
282     if(p_data[14] > 0x00)
283         p_group->i_justify = p_data[14];
284 
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);
288 
289     p_group->i_start = ParseTimeCode( &p_data[5], fps );
290     p_group->i_end = ParseTimeCode( &p_data[9], fps );
291 
292     /* Text Field */
293     for (size_t i = STL_TTI_HEADER_SIZE; i < STL_TTI_SIZE; i++)
294     {
295         const uint8_t code = p_data[i];
296         switch(code)
297         {
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;
304 
305             case STL_TF_END_FILL:
306                 TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
307                 ClearTeletextStyles(p_group);
308                 return true;
309 
310             default:
311                 if(code <= STL_TF_TELETEXT_LAST)
312                 {
313                     TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
314                     GroupParseTeletext(p_group, code);
315                 }
316                 else if((code >= STL_TF_CHARCODE1_FIRST && code <= STL_TF_CHARCODE1_LAST) ||
317                     code >= STL_TF_CHARCODE2_FIRST)
318                 {
319                     p_buffer[i_buffer++] = code;
320                 }
321                 else if(code >= STL_TF_ITALICS_ON && code <= STL_TF_BOXING_OFF)
322                 {
323                     TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
324                     GroupApplyStyle(p_group, code);
325                 }
326                 break;
327         }
328     }
329 
330     TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
331 
332     return false;
333 }
334 
FillSubpictureUpdater(stl_sg_t * p_group,subpicture_updater_sys_t * p_spu_sys)335 static void FillSubpictureUpdater(stl_sg_t *p_group, subpicture_updater_sys_t *p_spu_sys)
336 {
337     if(p_group->i_accumulating)
338     {
339         p_spu_sys->region.p_segments = text_segment_Copy(p_group->p_segment);
340     }
341     else
342     {
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;
346     }
347 
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;
353 }
354 
ResetGroups(decoder_sys_t * p_sys)355 static void ResetGroups(decoder_sys_t *p_sys)
356 {
357     for(size_t i=0; i<=STL_GROUPS_MAX; i++)
358     {
359         stl_sg_t *p_group = &p_sys->groups[i];
360         if(p_group->p_segment)
361         {
362             text_segment_ChainDelete(p_group->p_segment);
363             p_group->p_segment = NULL;
364             p_group->pp_segment_last = &p_group->p_segment;
365         }
366 
367         if(p_group->p_style)
368         {
369             text_style_Delete(p_group->p_style);
370             p_group->p_style = NULL;
371         }
372 
373         p_group->i_accumulating = false;
374         p_group->i_end = 0;
375         p_group->i_start = 0;
376         p_group->i_justify = 0;
377     }
378 }
379 
Decode(decoder_t * p_dec,block_t * p_block)380 static int Decode(decoder_t *p_dec, block_t *p_block)
381 {
382     if (p_block == NULL) /* No Drain */
383         return VLCDEC_SUCCESS;
384 
385     if(p_block->i_buffer < STL_TTI_SIZE)
386         p_block->i_flags |= BLOCK_FLAG_CORRUPTED;
387 
388     if(p_block->i_flags & (BLOCK_FLAG_CORRUPTED|BLOCK_FLAG_DISCONTINUITY))
389     {
390         ResetGroups(p_dec->p_sys);
391 
392         if(p_block->i_flags & BLOCK_FLAG_CORRUPTED)
393         {
394             block_Release(p_block);
395             return VLCDEC_SUCCESS;
396         }
397     }
398 
399     const char *psz_charset = cct_nums[p_dec->p_sys->cct - CCT_BEGIN].str;
400     for (size_t i = 0; i < p_block->i_buffer / STL_TTI_SIZE; i++)
401     {
402         stl_sg_t *p_group = &p_dec->p_sys->groups[p_block->p_buffer[0]];
403         if(ParseTTI(p_group, &p_block->p_buffer[i * STL_TTI_SIZE], psz_charset, p_dec->p_sys->i_fps) &&
404            p_group->p_segment != NULL )
405         {
406             /* output */
407             subpicture_t *p_sub = decoder_NewSubpictureText(p_dec);
408             if( p_sub )
409             {
410                 FillSubpictureUpdater(p_group, p_sub->updater.p_sys );
411 
412                 p_sub->b_absolute = false;
413 
414                 if(p_group->i_end && p_group->i_start >= p_block->i_dts)
415                 {
416                     p_sub->i_start = VLC_TS_0 + p_group->i_start;
417                     p_sub->i_stop =  VLC_TS_0 + p_group->i_end;
418                 }
419                 else
420                 {
421                     p_sub->i_start    = p_block->i_pts;
422                     p_sub->i_stop     = p_block->i_pts + p_block->i_length;
423                     p_sub->b_ephemer  = (p_block->i_length == 0);
424                 }
425                 decoder_QueueSub(p_dec, p_sub);
426             }
427         }
428     }
429 
430     ResetGroups(p_dec->p_sys);
431 
432     block_Release(p_block);
433     return VLCDEC_SUCCESS;
434 }
435 
ParseGSI(const decoder_t * dec,decoder_sys_t * p_sys)436 static int ParseGSI(const decoder_t *dec, decoder_sys_t *p_sys)
437 {
438     uint8_t *header = dec->fmt_in.p_extra;
439     if (!header) {
440         msg_Err(dec, "NULL EBU header (GSI block)\n");
441         return VLC_EGENERIC;
442     }
443 
444     if (GSI_BLOCK_SIZE != dec->fmt_in.i_extra) {
445         msg_Err(dec, "EBU header is not in expected size (%d)\n", dec->fmt_in.i_extra);
446         return VLC_EGENERIC;
447     }
448 
449     char dfc_fps_str[] = { header[6], header[7], '\0' };
450     int fps = strtol(dfc_fps_str, NULL, 10);
451     if (1 > fps || 60 < fps) {
452         msg_Warn(dec, "EBU header contains unsupported DFC fps ('%s'); falling back to 25\n", dfc_fps_str);
453         fps = 25;
454     }
455 
456     int cct = (header[12] << 8) | header[13];
457     if (CCT_BEGIN > cct || CCT_END < cct) {
458         msg_Err(dec, "EBU header contains illegal CCT (0x%x)\n", cct);
459         return VLC_EGENERIC;
460     }
461 
462     msg_Dbg(dec, "DFC fps=%d, CCT=0x%x", fps, cct);
463     p_sys->i_fps = fps;
464     p_sys->cct = cct;
465 
466     return VLC_SUCCESS;
467 }
468 
Open(vlc_object_t * object)469 static int Open(vlc_object_t *object)
470 {
471     decoder_t *dec = (decoder_t*)object;
472 
473     if (dec->fmt_in.i_codec != VLC_CODEC_EBU_STL)
474         return VLC_EGENERIC;
475 
476     decoder_sys_t *sys = calloc(1, sizeof(*sys));
477     if (!sys)
478         return VLC_ENOMEM;
479 
480     int rc = ParseGSI(dec, sys);
481     if (VLC_SUCCESS != rc)
482         return rc;
483 
484     for(size_t i=0; i<=STL_GROUPS_MAX; i++)
485         sys->groups[i].pp_segment_last = &sys->groups[i].p_segment;
486 
487     dec->p_sys = sys;
488     dec->pf_decode = Decode;
489     dec->fmt_out.i_codec = 0;
490     return VLC_SUCCESS;
491 }
492 
Close(vlc_object_t * object)493 static void Close(vlc_object_t *object)
494 {
495     decoder_t *dec = (decoder_t*)object;
496     decoder_sys_t *p_sys = dec->p_sys;
497 
498     ResetGroups(p_sys);
499     free(p_sys);
500 }
501