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