]> git.sesse.net Git - ffmpeg/blob - libavcodec/ccaption_dec.c
Merge commit 'e5997152f54f790229c99f237f8eb6b5b1ee683a'
[ffmpeg] / libavcodec / ccaption_dec.c
1 /*
2  * Closed Caption Decoding
3  * Copyright (c) 2015 Anshul Maheshwari
4  *
5  * This file is part of FFmpeg.
6  *
7  * FFmpeg is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * FFmpeg is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with FFmpeg; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21
22 #include "avcodec.h"
23 #include "ass.h"
24 #include "libavutil/opt.h"
25
26 #define SCREEN_ROWS 15
27 #define SCREEN_COLUMNS 32
28
29 #define SET_FLAG(var, val)   ( (var) |=   ( 1 << (val)) )
30 #define UNSET_FLAG(var, val) ( (var) &=  ~( 1 << (val)) )
31 #define CHECK_FLAG(var, val) ( (var) &    ( 1 << (val)) )
32
33 /*
34  * TODO list
35  * 1) handle font and color completely
36  */
37 enum cc_mode {
38     CCMODE_POPON,
39     CCMODE_PAINTON,
40     CCMODE_ROLLUP_2,
41     CCMODE_ROLLUP_3,
42     CCMODE_ROLLUP_4,
43     CCMODE_TEXT,
44 };
45
46 enum cc_color_code {
47     CCCOL_WHITE,
48     CCCOL_GREEN,
49     CCCOL_BLUE,
50     CCCOL_CYAN,
51     CCCOL_RED,
52     CCCOL_YELLOW,
53     CCCOL_MAGENTA,
54     CCCOL_USERDEFINED,
55     CCCOL_BLACK,
56     CCCOL_TRANSPARENT,
57 };
58
59 enum cc_font {
60     CCFONT_REGULAR,
61     CCFONT_ITALICS,
62     CCFONT_UNDERLINED,
63     CCFONT_UNDERLINED_ITALICS,
64 };
65
66 static const unsigned char pac2_attribs[32][3] = // Color, font, ident
67 {
68     { CCCOL_WHITE,   CCFONT_REGULAR,            0 },  // 0x40 || 0x60
69     { CCCOL_WHITE,   CCFONT_UNDERLINED,         0 },  // 0x41 || 0x61
70     { CCCOL_GREEN,   CCFONT_REGULAR,            0 },  // 0x42 || 0x62
71     { CCCOL_GREEN,   CCFONT_UNDERLINED,         0 },  // 0x43 || 0x63
72     { CCCOL_BLUE,    CCFONT_REGULAR,            0 },  // 0x44 || 0x64
73     { CCCOL_BLUE,    CCFONT_UNDERLINED,         0 },  // 0x45 || 0x65
74     { CCCOL_CYAN,    CCFONT_REGULAR,            0 },  // 0x46 || 0x66
75     { CCCOL_CYAN,    CCFONT_UNDERLINED,         0 },  // 0x47 || 0x67
76     { CCCOL_RED,     CCFONT_REGULAR,            0 },  // 0x48 || 0x68
77     { CCCOL_RED,     CCFONT_UNDERLINED,         0 },  // 0x49 || 0x69
78     { CCCOL_YELLOW,  CCFONT_REGULAR,            0 },  // 0x4a || 0x6a
79     { CCCOL_YELLOW,  CCFONT_UNDERLINED,         0 },  // 0x4b || 0x6b
80     { CCCOL_MAGENTA, CCFONT_REGULAR,            0 },  // 0x4c || 0x6c
81     { CCCOL_MAGENTA, CCFONT_UNDERLINED,         0 },  // 0x4d || 0x6d
82     { CCCOL_WHITE,   CCFONT_ITALICS,            0 },  // 0x4e || 0x6e
83     { CCCOL_WHITE,   CCFONT_UNDERLINED_ITALICS, 0 },  // 0x4f || 0x6f
84     { CCCOL_WHITE,   CCFONT_REGULAR,            0 },  // 0x50 || 0x70
85     { CCCOL_WHITE,   CCFONT_UNDERLINED,         0 },  // 0x51 || 0x71
86     { CCCOL_WHITE,   CCFONT_REGULAR,            4 },  // 0x52 || 0x72
87     { CCCOL_WHITE,   CCFONT_UNDERLINED,         4 },  // 0x53 || 0x73
88     { CCCOL_WHITE,   CCFONT_REGULAR,            8 },  // 0x54 || 0x74
89     { CCCOL_WHITE,   CCFONT_UNDERLINED,         8 },  // 0x55 || 0x75
90     { CCCOL_WHITE,   CCFONT_REGULAR,           12 },  // 0x56 || 0x76
91     { CCCOL_WHITE,   CCFONT_UNDERLINED,        12 },  // 0x57 || 0x77
92     { CCCOL_WHITE,   CCFONT_REGULAR,           16 },  // 0x58 || 0x78
93     { CCCOL_WHITE,   CCFONT_UNDERLINED,        16 },  // 0x59 || 0x79
94     { CCCOL_WHITE,   CCFONT_REGULAR,           20 },  // 0x5a || 0x7a
95     { CCCOL_WHITE,   CCFONT_UNDERLINED,        20 },  // 0x5b || 0x7b
96     { CCCOL_WHITE,   CCFONT_REGULAR,           24 },  // 0x5c || 0x7c
97     { CCCOL_WHITE,   CCFONT_UNDERLINED,        24 },  // 0x5d || 0x7d
98     { CCCOL_WHITE,   CCFONT_REGULAR,           28 },  // 0x5e || 0x7e
99     { CCCOL_WHITE,   CCFONT_UNDERLINED,        28 }   // 0x5f || 0x7f
100     /* total 32 entries */
101 };
102
103 /* 0-255 needs 256 spaces */
104 static const uint8_t parity_table[256] = { 0, 1, 1, 0, 1, 0, 0, 1,
105                                            1, 0, 0, 1, 0, 1, 1, 0,
106                                            1, 0, 0, 1, 0, 1, 1, 0,
107                                            0, 1, 1, 0, 1, 0, 0, 1,
108                                            1, 0, 0, 1, 0, 1, 1, 0,
109                                            0, 1, 1, 0, 1, 0, 0, 1,
110                                            0, 1, 1, 0, 1, 0, 0, 1,
111                                            1, 0, 0, 1, 0, 1, 1, 0,
112                                            1, 0, 0, 1, 0, 1, 1, 0,
113                                            0, 1, 1, 0, 1, 0, 0, 1,
114                                            0, 1, 1, 0, 1, 0, 0, 1,
115                                            1, 0, 0, 1, 0, 1, 1, 0,
116                                            0, 1, 1, 0, 1, 0, 0, 1,
117                                            1, 0, 0, 1, 0, 1, 1, 0,
118                                            1, 0, 0, 1, 0, 1, 1, 0,
119                                            0, 1, 1, 0, 1, 0, 0, 1,
120                                            1, 0, 0, 1, 0, 1, 1, 0,
121                                            0, 1, 1, 0, 1, 0, 0, 1,
122                                            0, 1, 1, 0, 1, 0, 0, 1,
123                                            1, 0, 0, 1, 0, 1, 1, 0,
124                                            0, 1, 1, 0, 1, 0, 0, 1,
125                                            1, 0, 0, 1, 0, 1, 1, 0,
126                                            1, 0, 0, 1, 0, 1, 1, 0,
127                                            0, 1, 1, 0, 1, 0, 0, 1,
128                                            0, 1, 1, 0, 1, 0, 0, 1,
129                                            1, 0, 0, 1, 0, 1, 1, 0,
130                                            1, 0, 0, 1, 0, 1, 1, 0,
131                                            0, 1, 1, 0, 1, 0, 0, 1,
132                                            1, 0, 0, 1, 0, 1, 1, 0,
133                                            0, 1, 1, 0, 1, 0, 0, 1,
134                                            0, 1, 1, 0, 1, 0, 0, 1,
135                                            1, 0, 0, 1, 0, 1, 1, 0 };
136
137 struct Screen {
138     /* +1 is used to compensate null character of string */
139     uint8_t characters[SCREEN_ROWS][SCREEN_COLUMNS+1];
140     uint8_t colors[SCREEN_ROWS][SCREEN_COLUMNS+1];
141     uint8_t fonts[SCREEN_ROWS][SCREEN_COLUMNS+1];
142     /*
143      * Bitmask of used rows; if a bit is not set, the
144      * corresponding row is not used.
145      * for setting row 1  use row | (1 << 0)
146      * for setting row 15 use row | (1 << 14)
147      */
148     int16_t  row_used;
149 };
150
151
152 typedef struct CCaptionSubContext {
153     AVClass *class;
154     struct Screen screen[2];
155     int active_screen;
156     uint8_t cursor_row;
157     uint8_t cursor_column;
158     uint8_t cursor_color;
159     uint8_t cursor_font;
160     AVBPrint buffer;
161     int screen_changed;
162     int rollup;
163     enum  cc_mode mode;
164     int64_t start_time;
165     /* visible screen time */
166     int64_t startv_time;
167     int64_t end_time;
168     char prev_cmd[2];
169     /* buffer to store pkt data */
170     AVBufferRef *pktbuf;
171 }CCaptionSubContext;
172
173
174 static av_cold int init_decoder(AVCodecContext *avctx)
175 {
176     int ret;
177     CCaptionSubContext *ctx = avctx->priv_data;
178
179     av_bprint_init(&ctx->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
180     /* taking by default roll up to 2 */
181     ctx->mode = CCMODE_ROLLUP_2;
182     ctx->rollup = 2;
183     ret = ff_ass_subtitle_header_default(avctx);
184     if(ret < 0) {
185         return ret;
186     }
187     /* allocate pkt buffer */
188     ctx->pktbuf = av_buffer_alloc(128);
189     if( !ctx->pktbuf) {
190         ret = AVERROR(ENOMEM);
191     }
192     return ret;
193 }
194
195 static av_cold int close_decoder(AVCodecContext *avctx)
196 {
197     CCaptionSubContext *ctx = avctx->priv_data;
198     av_bprint_finalize( &ctx->buffer, NULL);
199     av_buffer_unref(&ctx->pktbuf);
200     return 0;
201 }
202
203 /**
204  * @param ctx closed caption context just to print log
205  */
206 static int write_char (CCaptionSubContext *ctx, char *row,uint8_t col, char ch)
207 {
208     if(col < SCREEN_COLUMNS) {
209         row[col] = ch;
210         return 0;
211     }
212     /* We have extra space at end only for null character */
213     else if ( col == SCREEN_COLUMNS && ch == 0) {
214         row[col] = ch;
215         return 0;
216     }
217     else {
218         av_log(ctx, AV_LOG_WARNING,"Data Ignored since exceeding screen width\n");
219         return AVERROR_INVALIDDATA;
220     }
221 }
222
223 /**
224  * This function after validating parity bit, also remove it from data pair.
225  * The first byte doesn't pass parity, we replace it with a solid blank
226  * and process the pair.
227  * If the second byte doesn't pass parity, it returns INVALIDDATA
228  * user can ignore the whole pair and pass the other pair.
229  */
230 static int validate_cc_data_pair (uint8_t *cc_data_pair)
231 {
232     uint8_t cc_valid = (*cc_data_pair & 4) >>2;
233     uint8_t cc_type = *cc_data_pair & 3;
234
235     if (!cc_valid)
236         return AVERROR_INVALIDDATA;
237
238     // if EIA-608 data then verify parity.
239     if (cc_type==0 || cc_type==1) {
240         if (!parity_table[cc_data_pair[2]]) {
241             return AVERROR_INVALIDDATA;
242         }
243         if (!parity_table[cc_data_pair[1]]) {
244             cc_data_pair[1]=0x7F;
245         }
246     }
247
248     //Skip non-data
249     if( (cc_data_pair[0] == 0xFA || cc_data_pair[0] == 0xFC || cc_data_pair[0] == 0xFD )
250          && (cc_data_pair[1] & 0x7F) == 0 && (cc_data_pair[2] & 0x7F) == 0)
251         return AVERROR_PATCHWELCOME;
252
253     //skip 708 data
254     if(cc_type == 3 || cc_type == 2 )
255         return AVERROR_PATCHWELCOME;
256
257     /* remove parity bit */
258     cc_data_pair[1] &= 0x7F;
259     cc_data_pair[2] &= 0x7F;
260
261
262     return 0;
263
264 }
265
266 static struct Screen *get_writing_screen(CCaptionSubContext *ctx)
267 {
268     switch (ctx->mode) {
269     case CCMODE_POPON:
270         // use Inactive screen
271         return ctx->screen + !ctx->active_screen;
272     case CCMODE_PAINTON:
273     case CCMODE_ROLLUP_2:
274     case CCMODE_ROLLUP_3:
275     case CCMODE_ROLLUP_4:
276     case CCMODE_TEXT:
277         // use active screen
278         return ctx->screen + ctx->active_screen;
279     }
280     /* It was never an option */
281     return NULL;
282 }
283
284 static void roll_up(CCaptionSubContext *ctx)
285 {
286     struct Screen *screen;
287     int i, keep_lines;
288
289     if(ctx->mode == CCMODE_TEXT)
290         return;
291
292     screen = get_writing_screen(ctx);
293
294     /* +1 signify cursor_row starts from 0
295      * Can't keep lines less then row cursor pos
296      */
297     keep_lines = FFMIN(ctx->cursor_row + 1, ctx->rollup);
298
299     for( i = 0; i < ctx->cursor_row - keep_lines; i++ )
300         UNSET_FLAG(screen->row_used, i);
301
302
303     for( i = 0; i < keep_lines && screen->row_used; i++ ) {
304         const int i_row = ctx->cursor_row - keep_lines + i + 1;
305
306         memcpy( screen->characters[i_row], screen->characters[i_row+1], SCREEN_COLUMNS );
307         memcpy( screen->colors[i_row], screen->colors[i_row+1], SCREEN_COLUMNS);
308         memcpy( screen->fonts[i_row], screen->fonts[i_row+1], SCREEN_COLUMNS);
309         if(CHECK_FLAG(screen->row_used, i_row + 1))
310             SET_FLAG(screen->row_used, i_row);
311
312     }
313     UNSET_FLAG(screen->row_used, ctx->cursor_row);
314
315 }
316
317 static int reap_screen(CCaptionSubContext *ctx, int64_t pts)
318 {
319     int i;
320     int ret = 0;
321     struct Screen *screen = ctx->screen + ctx->active_screen;
322     ctx->start_time = ctx->startv_time;
323
324     for( i = 0; screen->row_used && i < SCREEN_ROWS; i++)
325     {
326         if(CHECK_FLAG(screen->row_used,i)) {
327             char *str = screen->characters[i];
328             /* skip space */
329             while (*str == ' ')
330                 str++;
331
332             av_bprintf(&ctx->buffer, "%s\\N", str);
333             ret = av_bprint_is_complete(&ctx->buffer);
334             if( ret == 0) {
335                 ret = AVERROR(ENOMEM);
336                 break;
337             }
338         }
339
340     }
341     if(screen->row_used && ctx->buffer.len >= 2 ) {
342         ctx->buffer.len -= 2;
343         ctx->buffer.str[ctx->buffer.len] = 0;
344     }
345     ctx->startv_time = pts;
346     ctx->end_time = pts;
347     return ret;
348 }
349
350 static void handle_textattr( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
351 {
352     int i = lo - 0x20;
353     int ret;
354     struct Screen *screen = get_writing_screen(ctx);
355     char *row = screen->characters[ctx->cursor_row];
356
357     if( i >= 32)
358         return;
359
360     ctx->cursor_color =  pac2_attribs[i][0];
361     ctx->cursor_font = pac2_attribs[i][1];
362
363     SET_FLAG(screen->row_used,ctx->cursor_row);
364     ret = write_char(ctx, row, ctx->cursor_column, ' ');
365     if(ret == 0)
366         ctx->cursor_column++;
367 }
368
369 static void handle_pac( CCaptionSubContext *ctx, uint8_t hi, uint8_t lo )
370 {
371     static const int8_t row_map[] = {
372         11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10
373     };
374     const int index = ( (hi<<1) & 0x0e) | ( (lo>>5) & 0x01 );
375     struct Screen *screen = get_writing_screen(ctx);
376     char *row;
377     int indent,i,ret;
378
379     if( row_map[index] <= 0 ) {
380         av_log(ctx, AV_LOG_DEBUG,"Invalid pac index encountered\n");
381         return;
382     }
383
384     lo &= 0x1f;
385
386     ctx->cursor_row = row_map[index] - 1;
387     ctx->cursor_color =  pac2_attribs[lo][0];
388     ctx->cursor_font = pac2_attribs[lo][1];
389     ctx->cursor_column = 0;
390     indent = pac2_attribs[lo][2];
391     row = screen->characters[ctx->cursor_row];
392     for(i = 0;i < indent; i++) {
393         ret = write_char(ctx, row, ctx->cursor_column, ' ');
394         if(  ret == 0 )
395             ctx->cursor_column++;
396     }
397
398 }
399
400 /**
401  * @param pts it is required to set end time
402  */
403 static int handle_edm(CCaptionSubContext *ctx,int64_t pts)
404 {
405     int ret = 0;
406     struct Screen *screen = ctx->screen + ctx->active_screen;
407
408     reap_screen(ctx, pts);
409     screen->row_used = 0;
410     ctx->screen_changed = 1;
411     return ret;
412 }
413
414 static int handle_eoc(CCaptionSubContext *ctx, int64_t pts)
415 {
416     int ret;
417     ret = handle_edm(ctx,pts);
418     ctx->active_screen = !ctx->active_screen;
419     ctx->cursor_column = 0;
420     return ret;
421 }
422
423 static void handle_delete_end_of_row( CCaptionSubContext *ctx, char hi, char lo)
424 {
425     struct Screen *screen = get_writing_screen(ctx);
426     char *row = screen->characters[ctx->cursor_row];
427     write_char(ctx, row, ctx->cursor_column, 0);
428
429 }
430
431 static void handle_char(CCaptionSubContext *ctx, char hi, char lo, int64_t pts)
432 {
433     struct Screen *screen = get_writing_screen(ctx);
434     char *row = screen->characters[ctx->cursor_row];
435     int ret;
436
437     SET_FLAG(screen->row_used,ctx->cursor_row);
438
439     ret = write_char(ctx, row, ctx->cursor_column, hi);
440     if( ret == 0 )
441         ctx->cursor_column++;
442
443     if(lo) {
444         ret = write_char(ctx, row, ctx->cursor_column, lo);
445         if ( ret == 0 )
446             ctx->cursor_column++;
447     }
448     write_char(ctx, row, ctx->cursor_column, 0);
449
450     /* reset prev command since character can repeat */
451     ctx->prev_cmd[0] = 0;
452     ctx->prev_cmd[1] = 0;
453     if (lo)
454        av_dlog(ctx, "(%c,%c)\n",hi,lo);
455     else
456        av_dlog(ctx, "(%c)\n",hi);
457 }
458
459 static int process_cc608(CCaptionSubContext *ctx, int64_t pts, uint8_t hi, uint8_t lo)
460 {
461     int ret = 0;
462 #define COR3(var, with1, with2, with3)  ( (var) == (with1) ||  (var) == (with2) || (var) == (with3) )
463     if ( hi == ctx->prev_cmd[0] && lo == ctx->prev_cmd[1]) {
464     /* ignore redundant command */
465     } else if ( (hi == 0x10 && (lo >= 0x40 || lo <= 0x5f)) ||
466               ( (hi >= 0x11 && hi <= 0x17) && (lo >= 0x40 && lo <= 0x7f) ) ) {
467         handle_pac(ctx, hi, lo);
468     } else if ( ( hi == 0x11 && lo >= 0x20 && lo <= 0x2f ) ||
469                 ( hi == 0x17 && lo >= 0x2e && lo <= 0x2f) ) {
470         handle_textattr(ctx, hi, lo);
471     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x20 ) {
472     /* resume caption loading */
473         ctx->mode = CCMODE_POPON;
474     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x24 ) {
475         handle_delete_end_of_row(ctx, hi, lo);
476     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x25 ) {
477         ctx->rollup = 2;
478         ctx->mode = CCMODE_ROLLUP_2;
479     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x26 ) {
480         ctx->rollup = 3;
481         ctx->mode = CCMODE_ROLLUP_3;
482     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x27 ) {
483         ctx->rollup = 4;
484         ctx->mode = CCMODE_ROLLUP_4;
485     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x29 ) {
486     /* resume direct captioning */
487         ctx->mode = CCMODE_PAINTON;
488     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2B ) {
489     /* resume text display */
490         ctx->mode = CCMODE_TEXT;
491     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2C ) {
492     /* erase display memory */
493         ret = handle_edm(ctx, pts);
494     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2D ) {
495     /* carriage return */
496         av_dlog(ctx, "carriage return\n");
497         reap_screen(ctx, pts);
498         roll_up(ctx);
499         ctx->screen_changed = 1;
500         ctx->cursor_column = 0;
501     } else if ( COR3(hi, 0x14, 0x15, 0x1C) && lo == 0x2F ) {
502     /* end of caption */
503         av_dlog(ctx, "handle_eoc\n");
504         ret = handle_eoc(ctx, pts);
505     } else if (hi>=0x20) {
506     /* Standard characters (always in pairs) */
507         handle_char(ctx, hi, lo, pts);
508     } else {
509     /* Ignoring all other non data code */
510         av_dlog(ctx, "Unknown command 0x%hhx 0x%hhx\n", hi, lo);
511     }
512
513     /* set prev command */
514      ctx->prev_cmd[0] = hi;
515      ctx->prev_cmd[1] = lo;
516
517 #undef COR3
518     return ret;
519
520 }
521
522 static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avpkt)
523 {
524     CCaptionSubContext *ctx = avctx->priv_data;
525     AVSubtitle *sub = data;
526     uint8_t *bptr = NULL;
527     int len = avpkt->size;
528     int ret = 0;
529     int i;
530
531     if ( ctx->pktbuf->size < len) {
532         ret = av_buffer_realloc(&ctx->pktbuf, len);
533          if(ret < 0) {
534             av_log(ctx, AV_LOG_WARNING, "Insufficient Memory of %d truncated to %d\n",len, ctx->pktbuf->size);
535             len = ctx->pktbuf->size;
536             ret = 0;
537         }
538     }
539     memcpy(ctx->pktbuf->data, avpkt->data, len);
540     bptr = ctx->pktbuf->data;
541
542
543     for (i  = 0; i < len; i += 3) {
544         uint8_t cc_type = *(bptr + i) & 3;
545         if (validate_cc_data_pair( bptr + i) )
546             continue;
547         /* ignoring data field 1 */
548         if(cc_type == 1)
549             continue;
550         else
551             process_cc608(ctx, avpkt->pts, *(bptr + i + 1) & 0x7f, *(bptr + i + 2) & 0x7f);
552         if(ctx->screen_changed && *ctx->buffer.str)
553         {
554             int start_time = av_rescale_q(ctx->start_time, avctx->time_base, (AVRational){ 1, 100 });
555             int end_time = av_rescale_q(ctx->end_time, avctx->time_base, (AVRational){ 1, 100 });
556             av_dlog(ctx, "cdp writing data (%s)\n",ctx->buffer.str);
557             ret = ff_ass_add_rect_bprint(sub, &ctx->buffer, start_time, end_time - start_time);
558             if (ret < 0)
559                 return ret;
560             sub->pts = av_rescale_q(ctx->start_time, avctx->time_base, AV_TIME_BASE_Q);
561             ctx->screen_changed = 0;
562             av_bprint_clear(&ctx->buffer);
563         }
564     }
565
566     *got_sub = sub->num_rects > 0;
567     return ret;
568 }
569
570 static const AVOption options[] = {
571     {NULL}
572 };
573
574 static const AVClass ccaption_dec_class = {
575     .class_name = "Closed caption Decoder",
576     .item_name  = av_default_item_name,
577     .option     = options,
578     .version    = LIBAVUTIL_VERSION_INT,
579 };
580
581 AVCodec ff_ccaption_decoder = {
582     .name           = "cc_dec",
583     .long_name      = NULL_IF_CONFIG_SMALL("Closed Caption (EIA-608 / CEA-708) Decoder"),
584     .type           = AVMEDIA_TYPE_SUBTITLE,
585     .id             = AV_CODEC_ID_EIA_608,
586     .priv_data_size = sizeof(CCaptionSubContext),
587     .init           = init_decoder,
588     .close          = close_decoder,
589     .decode         = decode,
590     .priv_class     = &ccaption_dec_class,
591 };