blob: 687b6af1293b06ed36d7ce8be9dfe87362697a92
1 | /* |
2 | * Teletext decoding for ffmpeg |
3 | * Copyright (c) 2005-2010, 2012 Wolfram Gloger |
4 | * Copyright (c) 2013 Marton Balint |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with this library; if not, write to the Free Software |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | */ |
20 | |
21 | #include "avcodec.h" |
22 | #include "libavcodec/ass.h" |
23 | #include "libavcodec/dvbtxt.h" |
24 | #include "libavutil/opt.h" |
25 | #include "libavutil/bprint.h" |
26 | #include "libavutil/internal.h" |
27 | #include "libavutil/intreadwrite.h" |
28 | #include "libavutil/log.h" |
29 | |
30 | #include <libzvbi.h> |
31 | |
32 | #define TEXT_MAXSZ (25 * (56 + 1) * 4 + 2) |
33 | #define VBI_NB_COLORS 40 |
34 | #define VBI_TRANSPARENT_BLACK 8 |
35 | #define RGBA(r,g,b,a) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) |
36 | #define VBI_R(rgba) (((rgba) >> 0) & 0xFF) |
37 | #define VBI_G(rgba) (((rgba) >> 8) & 0xFF) |
38 | #define VBI_B(rgba) (((rgba) >> 16) & 0xFF) |
39 | #define VBI_A(rgba) (((rgba) >> 24) & 0xFF) |
40 | #define MAX_BUFFERED_PAGES 25 |
41 | #define BITMAP_CHAR_WIDTH 12 |
42 | #define BITMAP_CHAR_HEIGHT 10 |
43 | #define MAX_SLICES 64 |
44 | |
45 | typedef struct TeletextPage |
46 | { |
47 | AVSubtitleRect *sub_rect; |
48 | int pgno; |
49 | int subno; |
50 | int64_t pts; |
51 | } TeletextPage; |
52 | |
53 | typedef struct TeletextContext |
54 | { |
55 | AVClass *class; |
56 | char *pgno; |
57 | int x_offset; |
58 | int y_offset; |
59 | int format_id; /* 0 = bitmap, 1 = text/ass */ |
60 | int chop_top; |
61 | int sub_duration; /* in msec */ |
62 | int transparent_bg; |
63 | int opacity; |
64 | int chop_spaces; |
65 | |
66 | int lines_processed; |
67 | TeletextPage *pages; |
68 | int nb_pages; |
69 | int64_t pts; |
70 | int handler_ret; |
71 | |
72 | vbi_decoder * vbi; |
73 | #ifdef DEBUG |
74 | vbi_export * ex; |
75 | #endif |
76 | vbi_sliced sliced[MAX_SLICES]; |
77 | |
78 | int readorder; |
79 | } TeletextContext; |
80 | |
81 | static int chop_spaces_utf8(const unsigned char* t, int len) |
82 | { |
83 | t += len; |
84 | while (len > 0) { |
85 | if (*--t != ' ' || (len-1 > 0 && *(t-1) & 0x80)) |
86 | break; |
87 | --len; |
88 | } |
89 | return len; |
90 | } |
91 | |
92 | static void subtitle_rect_free(AVSubtitleRect **sub_rect) |
93 | { |
94 | av_freep(&(*sub_rect)->data[0]); |
95 | av_freep(&(*sub_rect)->data[1]); |
96 | av_freep(&(*sub_rect)->ass); |
97 | av_freep(sub_rect); |
98 | } |
99 | |
100 | static char *create_ass_text(TeletextContext *ctx, const char *text) |
101 | { |
102 | char *dialog; |
103 | AVBPrint buf; |
104 | |
105 | av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); |
106 | ff_ass_bprint_text_event(&buf, text, strlen(text), "", 0); |
107 | if (!av_bprint_is_complete(&buf)) { |
108 | av_bprint_finalize(&buf, NULL); |
109 | return NULL; |
110 | } |
111 | dialog = ff_ass_get_dialog(ctx->readorder++, 0, NULL, NULL, buf.str); |
112 | av_bprint_finalize(&buf, NULL); |
113 | return dialog; |
114 | } |
115 | |
116 | /* Draw a page as text */ |
117 | static int gen_sub_text(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, int chop_top) |
118 | { |
119 | const char *in; |
120 | AVBPrint buf; |
121 | char *vbi_text = av_malloc(TEXT_MAXSZ); |
122 | int sz; |
123 | |
124 | if (!vbi_text) |
125 | return AVERROR(ENOMEM); |
126 | |
127 | sz = vbi_print_page_region(page, vbi_text, TEXT_MAXSZ-1, "UTF-8", |
128 | /*table mode*/ TRUE, FALSE, |
129 | 0, chop_top, |
130 | page->columns, page->rows-chop_top); |
131 | if (sz <= 0) { |
132 | av_log(ctx, AV_LOG_ERROR, "vbi_print error\n"); |
133 | av_free(vbi_text); |
134 | return AVERROR_EXTERNAL; |
135 | } |
136 | vbi_text[sz] = '\0'; |
137 | in = vbi_text; |
138 | av_bprint_init(&buf, 0, TEXT_MAXSZ); |
139 | |
140 | if (ctx->chop_spaces) { |
141 | for (;;) { |
142 | int nl, sz; |
143 | |
144 | // skip leading spaces and newlines |
145 | in += strspn(in, " \n"); |
146 | // compute end of row |
147 | for (nl = 0; in[nl]; ++nl) |
148 | if (in[nl] == '\n' && (nl==0 || !(in[nl-1] & 0x80))) |
149 | break; |
150 | if (!in[nl]) |
151 | break; |
152 | // skip trailing spaces |
153 | sz = chop_spaces_utf8(in, nl); |
154 | av_bprint_append_data(&buf, in, sz); |
155 | av_bprintf(&buf, "\n"); |
156 | in += nl; |
157 | } |
158 | } else { |
159 | av_bprintf(&buf, "%s\n", vbi_text); |
160 | } |
161 | av_free(vbi_text); |
162 | |
163 | if (!av_bprint_is_complete(&buf)) { |
164 | av_bprint_finalize(&buf, NULL); |
165 | return AVERROR(ENOMEM); |
166 | } |
167 | |
168 | if (buf.len) { |
169 | sub_rect->type = SUBTITLE_ASS; |
170 | sub_rect->ass = create_ass_text(ctx, buf.str); |
171 | |
172 | if (!sub_rect->ass) { |
173 | av_bprint_finalize(&buf, NULL); |
174 | return AVERROR(ENOMEM); |
175 | } |
176 | av_log(ctx, AV_LOG_DEBUG, "subtext:%s:txetbus\n", sub_rect->ass); |
177 | } else { |
178 | sub_rect->type = SUBTITLE_NONE; |
179 | } |
180 | av_bprint_finalize(&buf, NULL); |
181 | return 0; |
182 | } |
183 | |
184 | static void fix_transparency(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, |
185 | int chop_top, int resx, int resy) |
186 | { |
187 | int iy; |
188 | |
189 | // Hack for transparency, inspired by VLC code... |
190 | for (iy = 0; iy < resy; iy++) { |
191 | uint8_t *pixel = sub_rect->data[0] + iy * sub_rect->linesize[0]; |
192 | vbi_char *vc = page->text + (iy / BITMAP_CHAR_HEIGHT + chop_top) * page->columns; |
193 | vbi_char *vcnext = vc + page->columns; |
194 | for (; vc < vcnext; vc++) { |
195 | uint8_t *pixelnext = pixel + BITMAP_CHAR_WIDTH; |
196 | switch (vc->opacity) { |
197 | case VBI_TRANSPARENT_SPACE: |
198 | memset(pixel, VBI_TRANSPARENT_BLACK, BITMAP_CHAR_WIDTH); |
199 | break; |
200 | case VBI_OPAQUE: |
201 | if (!ctx->transparent_bg) |
202 | break; |
203 | case VBI_SEMI_TRANSPARENT: |
204 | if (ctx->opacity > 0) { |
205 | if (ctx->opacity < 255) |
206 | for(; pixel < pixelnext; pixel++) |
207 | if (*pixel == vc->background) |
208 | *pixel += VBI_NB_COLORS; |
209 | break; |
210 | } |
211 | case VBI_TRANSPARENT_FULL: |
212 | for(; pixel < pixelnext; pixel++) |
213 | if (*pixel == vc->background) |
214 | *pixel = VBI_TRANSPARENT_BLACK; |
215 | break; |
216 | } |
217 | pixel = pixelnext; |
218 | } |
219 | } |
220 | } |
221 | |
222 | /* Draw a page as bitmap */ |
223 | static int gen_sub_bitmap(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page *page, int chop_top) |
224 | { |
225 | int resx = page->columns * BITMAP_CHAR_WIDTH; |
226 | int resy = (page->rows - chop_top) * BITMAP_CHAR_HEIGHT; |
227 | uint8_t ci; |
228 | vbi_char *vc = page->text + (chop_top * page->columns); |
229 | vbi_char *vcend = page->text + (page->rows * page->columns); |
230 | |
231 | for (; vc < vcend; vc++) { |
232 | if (vc->opacity != VBI_TRANSPARENT_SPACE) |
233 | break; |
234 | } |
235 | |
236 | if (vc >= vcend) { |
237 | av_log(ctx, AV_LOG_DEBUG, "dropping empty page %3x\n", page->pgno); |
238 | sub_rect->type = SUBTITLE_NONE; |
239 | return 0; |
240 | } |
241 | |
242 | sub_rect->data[0] = av_mallocz(resx * resy); |
243 | sub_rect->linesize[0] = resx; |
244 | if (!sub_rect->data[0]) |
245 | return AVERROR(ENOMEM); |
246 | |
247 | vbi_draw_vt_page_region(page, VBI_PIXFMT_PAL8, |
248 | sub_rect->data[0], sub_rect->linesize[0], |
249 | 0, chop_top, page->columns, page->rows - chop_top, |
250 | /*reveal*/ 1, /*flash*/ 1); |
251 | |
252 | fix_transparency(ctx, sub_rect, page, chop_top, resx, resy); |
253 | sub_rect->x = ctx->x_offset; |
254 | sub_rect->y = ctx->y_offset + chop_top * BITMAP_CHAR_HEIGHT; |
255 | sub_rect->w = resx; |
256 | sub_rect->h = resy; |
257 | sub_rect->nb_colors = ctx->opacity > 0 && ctx->opacity < 255 ? 2 * VBI_NB_COLORS : VBI_NB_COLORS; |
258 | sub_rect->data[1] = av_mallocz(AVPALETTE_SIZE); |
259 | if (!sub_rect->data[1]) { |
260 | av_freep(&sub_rect->data[0]); |
261 | return AVERROR(ENOMEM); |
262 | } |
263 | for (ci = 0; ci < VBI_NB_COLORS; ci++) { |
264 | int r, g, b, a; |
265 | |
266 | r = VBI_R(page->color_map[ci]); |
267 | g = VBI_G(page->color_map[ci]); |
268 | b = VBI_B(page->color_map[ci]); |
269 | a = VBI_A(page->color_map[ci]); |
270 | ((uint32_t *)sub_rect->data[1])[ci] = RGBA(r, g, b, a); |
271 | ((uint32_t *)sub_rect->data[1])[ci + VBI_NB_COLORS] = RGBA(r, g, b, ctx->opacity); |
272 | ff_dlog(ctx, "palette %0x\n", ((uint32_t *)sub_rect->data[1])[ci]); |
273 | } |
274 | ((uint32_t *)sub_rect->data[1])[VBI_TRANSPARENT_BLACK] = RGBA(0, 0, 0, 0); |
275 | ((uint32_t *)sub_rect->data[1])[VBI_TRANSPARENT_BLACK + VBI_NB_COLORS] = RGBA(0, 0, 0, 0); |
276 | sub_rect->type = SUBTITLE_BITMAP; |
277 | return 0; |
278 | } |
279 | |
280 | static void handler(vbi_event *ev, void *user_data) |
281 | { |
282 | TeletextContext *ctx = user_data; |
283 | TeletextPage *new_pages; |
284 | vbi_page page; |
285 | int res; |
286 | char pgno_str[12]; |
287 | vbi_subno subno; |
288 | vbi_page_type vpt; |
289 | int chop_top; |
290 | char *lang; |
291 | |
292 | snprintf(pgno_str, sizeof pgno_str, "%03x", ev->ev.ttx_page.pgno); |
293 | av_log(ctx, AV_LOG_DEBUG, "decoded page %s.%02x\n", |
294 | pgno_str, ev->ev.ttx_page.subno & 0xFF); |
295 | |
296 | if (strcmp(ctx->pgno, "*") && !strstr(ctx->pgno, pgno_str)) |
297 | return; |
298 | if (ctx->handler_ret < 0) |
299 | return; |
300 | |
301 | res = vbi_fetch_vt_page(ctx->vbi, &page, |
302 | ev->ev.ttx_page.pgno, |
303 | ev->ev.ttx_page.subno, |
304 | VBI_WST_LEVEL_3p5, 25, TRUE); |
305 | |
306 | if (!res) |
307 | return; |
308 | |
309 | #ifdef DEBUG |
310 | fprintf(stderr, "\nSaving res=%d dy0=%d dy1=%d...\n", |
311 | res, page.dirty.y0, page.dirty.y1); |
312 | fflush(stderr); |
313 | |
314 | if (!vbi_export_stdio(ctx->ex, stderr, &page)) |
315 | fprintf(stderr, "failed: %s\n", vbi_export_errstr(ctx->ex)); |
316 | #endif |
317 | |
318 | vpt = vbi_classify_page(ctx->vbi, ev->ev.ttx_page.pgno, &subno, &lang); |
319 | chop_top = ctx->chop_top || |
320 | ((page.rows > 1) && (vpt == VBI_SUBTITLE_PAGE)); |
321 | |
322 | av_log(ctx, AV_LOG_DEBUG, "%d x %d page chop:%d\n", |
323 | page.columns, page.rows, chop_top); |
324 | |
325 | if (ctx->nb_pages < MAX_BUFFERED_PAGES) { |
326 | if ((new_pages = av_realloc_array(ctx->pages, ctx->nb_pages + 1, sizeof(TeletextPage)))) { |
327 | TeletextPage *cur_page = new_pages + ctx->nb_pages; |
328 | ctx->pages = new_pages; |
329 | cur_page->sub_rect = av_mallocz(sizeof(*cur_page->sub_rect)); |
330 | cur_page->pts = ctx->pts; |
331 | cur_page->pgno = ev->ev.ttx_page.pgno; |
332 | cur_page->subno = ev->ev.ttx_page.subno; |
333 | if (cur_page->sub_rect) { |
334 | res = (ctx->format_id == 0) ? |
335 | gen_sub_bitmap(ctx, cur_page->sub_rect, &page, chop_top) : |
336 | gen_sub_text (ctx, cur_page->sub_rect, &page, chop_top); |
337 | if (res < 0) { |
338 | av_freep(&cur_page->sub_rect); |
339 | ctx->handler_ret = res; |
340 | } else { |
341 | ctx->pages[ctx->nb_pages++] = *cur_page; |
342 | } |
343 | } else { |
344 | ctx->handler_ret = AVERROR(ENOMEM); |
345 | } |
346 | } else { |
347 | ctx->handler_ret = AVERROR(ENOMEM); |
348 | } |
349 | } else { |
350 | //TODO: If multiple packets contain more than one page, pages may got queued up, and this may happen... |
351 | av_log(ctx, AV_LOG_ERROR, "Buffered too many pages, dropping page %s.\n", pgno_str); |
352 | ctx->handler_ret = AVERROR(ENOSYS); |
353 | } |
354 | |
355 | vbi_unref_page(&page); |
356 | } |
357 | |
358 | static int slice_to_vbi_lines(TeletextContext *ctx, uint8_t* buf, int size) |
359 | { |
360 | int lines = 0; |
361 | while (size >= 2 && lines < MAX_SLICES) { |
362 | int data_unit_id = buf[0]; |
363 | int data_unit_length = buf[1]; |
364 | if (data_unit_length + 2 > size) |
365 | return AVERROR_INVALIDDATA; |
366 | if (ff_data_unit_id_is_teletext(data_unit_id)) { |
367 | if (data_unit_length != 0x2c) |
368 | return AVERROR_INVALIDDATA; |
369 | else { |
370 | int line_offset = buf[2] & 0x1f; |
371 | int field_parity = buf[2] & 0x20; |
372 | int i; |
373 | ctx->sliced[lines].id = VBI_SLICED_TELETEXT_B; |
374 | ctx->sliced[lines].line = (line_offset > 0 ? (line_offset + (field_parity ? 0 : 313)) : 0); |
375 | for (i = 0; i < 42; i++) |
376 | ctx->sliced[lines].data[i] = vbi_rev8(buf[4 + i]); |
377 | lines++; |
378 | } |
379 | } |
380 | size -= data_unit_length + 2; |
381 | buf += data_unit_length + 2; |
382 | } |
383 | if (size) |
384 | av_log(ctx, AV_LOG_WARNING, "%d bytes remained after slicing data\n", size); |
385 | return lines; |
386 | } |
387 | |
388 | static int teletext_decode_frame(AVCodecContext *avctx, void *data, int *data_size, AVPacket *pkt) |
389 | { |
390 | TeletextContext *ctx = avctx->priv_data; |
391 | AVSubtitle *sub = data; |
392 | int ret = 0; |
393 | int j; |
394 | |
395 | if (!ctx->vbi) { |
396 | if (!(ctx->vbi = vbi_decoder_new())) |
397 | return AVERROR(ENOMEM); |
398 | if (!vbi_event_handler_register(ctx->vbi, VBI_EVENT_TTX_PAGE, handler, ctx)) { |
399 | vbi_decoder_delete(ctx->vbi); |
400 | ctx->vbi = NULL; |
401 | return AVERROR(ENOMEM); |
402 | } |
403 | } |
404 | |
405 | if (avctx->pkt_timebase.num && pkt->pts != AV_NOPTS_VALUE) |
406 | ctx->pts = av_rescale_q(pkt->pts, avctx->pkt_timebase, AV_TIME_BASE_Q); |
407 | |
408 | if (pkt->size) { |
409 | int lines; |
410 | const int full_pes_size = pkt->size + 45; /* PES header is 45 bytes */ |
411 | |
412 | // We allow unreasonably big packets, even if the standard only allows a max size of 1472 |
413 | if (full_pes_size < 184 || full_pes_size > 65504 || full_pes_size % 184 != 0) |
414 | return AVERROR_INVALIDDATA; |
415 | |
416 | ctx->handler_ret = pkt->size; |
417 | |
418 | if (ff_data_identifier_is_teletext(*pkt->data)) { |
419 | if ((lines = slice_to_vbi_lines(ctx, pkt->data + 1, pkt->size - 1)) < 0) |
420 | return lines; |
421 | ff_dlog(avctx, "ctx=%p buf_size=%d lines=%u pkt_pts=%7.3f\n", |
422 | ctx, pkt->size, lines, (double)pkt->pts/90000.0); |
423 | if (lines > 0) { |
424 | #ifdef DEBUG |
425 | int i; |
426 | av_log(avctx, AV_LOG_DEBUG, "line numbers:"); |
427 | for(i = 0; i < lines; i++) |
428 | av_log(avctx, AV_LOG_DEBUG, " %d", ctx->sliced[i].line); |
429 | av_log(avctx, AV_LOG_DEBUG, "\n"); |
430 | #endif |
431 | vbi_decode(ctx->vbi, ctx->sliced, lines, 0.0); |
432 | ctx->lines_processed += lines; |
433 | } |
434 | } |
435 | ctx->pts = AV_NOPTS_VALUE; |
436 | ret = ctx->handler_ret; |
437 | } |
438 | |
439 | if (ret < 0) |
440 | return ret; |
441 | |
442 | // is there a subtitle to pass? |
443 | if (ctx->nb_pages) { |
444 | int i; |
445 | sub->format = ctx->format_id; |
446 | sub->start_display_time = 0; |
447 | sub->end_display_time = ctx->sub_duration; |
448 | sub->num_rects = 0; |
449 | sub->pts = ctx->pages->pts; |
450 | |
451 | if (ctx->pages->sub_rect->type != SUBTITLE_NONE) { |
452 | sub->rects = av_malloc(sizeof(*sub->rects)); |
453 | if (sub->rects) { |
454 | sub->num_rects = 1; |
455 | sub->rects[0] = ctx->pages->sub_rect; |
456 | #if FF_API_AVPICTURE |
457 | FF_DISABLE_DEPRECATION_WARNINGS |
458 | for (j = 0; j < 4; j++) { |
459 | sub->rects[0]->pict.data[j] = sub->rects[0]->data[j]; |
460 | sub->rects[0]->pict.linesize[j] = sub->rects[0]->linesize[j]; |
461 | } |
462 | FF_ENABLE_DEPRECATION_WARNINGS |
463 | #endif |
464 | } else { |
465 | ret = AVERROR(ENOMEM); |
466 | } |
467 | } else { |
468 | av_log(avctx, AV_LOG_DEBUG, "sending empty sub\n"); |
469 | sub->rects = NULL; |
470 | } |
471 | if (!sub->rects) // no rect was passed |
472 | subtitle_rect_free(&ctx->pages->sub_rect); |
473 | |
474 | for (i = 0; i < ctx->nb_pages - 1; i++) |
475 | ctx->pages[i] = ctx->pages[i + 1]; |
476 | ctx->nb_pages--; |
477 | |
478 | if (ret >= 0) |
479 | *data_size = 1; |
480 | } else |
481 | *data_size = 0; |
482 | |
483 | return ret; |
484 | } |
485 | |
486 | static int teletext_init_decoder(AVCodecContext *avctx) |
487 | { |
488 | TeletextContext *ctx = avctx->priv_data; |
489 | unsigned int maj, min, rev; |
490 | |
491 | vbi_version(&maj, &min, &rev); |
492 | if (!(maj > 0 || min > 2 || min == 2 && rev >= 26)) { |
493 | av_log(avctx, AV_LOG_ERROR, "decoder needs zvbi version >= 0.2.26.\n"); |
494 | return AVERROR_EXTERNAL; |
495 | } |
496 | |
497 | if (ctx->format_id == 0) { |
498 | avctx->width = 41 * BITMAP_CHAR_WIDTH; |
499 | avctx->height = 25 * BITMAP_CHAR_HEIGHT; |
500 | } |
501 | |
502 | ctx->vbi = NULL; |
503 | ctx->pts = AV_NOPTS_VALUE; |
504 | |
505 | if (ctx->opacity == -1) |
506 | ctx->opacity = ctx->transparent_bg ? 0 : 255; |
507 | |
508 | #ifdef DEBUG |
509 | { |
510 | char *t; |
511 | ctx->ex = vbi_export_new("text", &t); |
512 | } |
513 | #endif |
514 | av_log(avctx, AV_LOG_VERBOSE, "page filter: %s\n", ctx->pgno); |
515 | return (ctx->format_id == 1) ? ff_ass_subtitle_header_default(avctx) : 0; |
516 | } |
517 | |
518 | static int teletext_close_decoder(AVCodecContext *avctx) |
519 | { |
520 | TeletextContext *ctx = avctx->priv_data; |
521 | |
522 | ff_dlog(avctx, "lines_total=%u\n", ctx->lines_processed); |
523 | while (ctx->nb_pages) |
524 | subtitle_rect_free(&ctx->pages[--ctx->nb_pages].sub_rect); |
525 | av_freep(&ctx->pages); |
526 | |
527 | vbi_decoder_delete(ctx->vbi); |
528 | ctx->vbi = NULL; |
529 | ctx->pts = AV_NOPTS_VALUE; |
530 | if (!(avctx->flags2 & AV_CODEC_FLAG2_RO_FLUSH_NOOP)) |
531 | ctx->readorder = 0; |
532 | return 0; |
533 | } |
534 | |
535 | static void teletext_flush(AVCodecContext *avctx) |
536 | { |
537 | teletext_close_decoder(avctx); |
538 | } |
539 | |
540 | #define OFFSET(x) offsetof(TeletextContext, x) |
541 | #define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM |
542 | static const AVOption options[] = { |
543 | {"txt_page", "list of teletext page numbers to decode, * is all", OFFSET(pgno), AV_OPT_TYPE_STRING, {.str = "*"}, 0, 0, SD}, |
544 | {"txt_chop_top", "discards the top teletext line", OFFSET(chop_top), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, SD}, |
545 | {"txt_format", "format of the subtitles (bitmap or text)", OFFSET(format_id), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, SD, "txt_format"}, |
546 | {"bitmap", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0}, 0, 0, SD, "txt_format"}, |
547 | {"text", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1}, 0, 0, SD, "txt_format"}, |
548 | {"txt_left", "x offset of generated bitmaps", OFFSET(x_offset), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 65535, SD}, |
549 | {"txt_top", "y offset of generated bitmaps", OFFSET(y_offset), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 65535, SD}, |
550 | {"txt_chop_spaces", "chops leading and trailing spaces from text", OFFSET(chop_spaces), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, SD}, |
551 | {"txt_duration", "display duration of teletext pages in msecs", OFFSET(sub_duration), AV_OPT_TYPE_INT, {.i64 = 30000}, 0, 86400000, SD}, |
552 | {"txt_transparent", "force transparent background of the teletext", OFFSET(transparent_bg), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, SD}, |
553 | {"txt_opacity", "set opacity of the transparent background", OFFSET(opacity), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 255, SD}, |
554 | { NULL }, |
555 | }; |
556 | |
557 | static const AVClass teletext_class = { |
558 | .class_name = "libzvbi_teletextdec", |
559 | .item_name = av_default_item_name, |
560 | .option = options, |
561 | .version = LIBAVUTIL_VERSION_INT, |
562 | }; |
563 | |
564 | AVCodec ff_libzvbi_teletext_decoder = { |
565 | .name = "libzvbi_teletextdec", |
566 | .long_name = NULL_IF_CONFIG_SMALL("Libzvbi DVB teletext decoder"), |
567 | .type = AVMEDIA_TYPE_SUBTITLE, |
568 | .id = AV_CODEC_ID_DVB_TELETEXT, |
569 | .priv_data_size = sizeof(TeletextContext), |
570 | .init = teletext_init_decoder, |
571 | .close = teletext_close_decoder, |
572 | .decode = teletext_decode_frame, |
573 | .capabilities = AV_CODEC_CAP_DELAY, |
574 | .flush = teletext_flush, |
575 | .priv_class= &teletext_class, |
576 | }; |
577 |