From patchwork Wed Apr 25 02:07:58 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Aman Karmani X-Patchwork-Id: 8642 Delivered-To: ffmpegpatchwork@gmail.com Received: by 2002:a02:155:0:0:0:0:0 with SMTP id c82-v6csp179783jad; Tue, 24 Apr 2018 19:08:15 -0700 (PDT) X-Google-Smtp-Source: AIpwx4/GyHZe+y4FooqvTOEpXeHG9J5pl3IL46pbBaBN6zKfyWWlqz0EXxezO3uDdG3ReoOB8A2r X-Received: by 2002:adf:ab56:: with SMTP id r22-v6mr22759346wrc.228.1524622095135; Tue, 24 Apr 2018 19:08:15 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1524622095; cv=none; d=google.com; s=arc-20160816; b=XKGYga+MDo7B7EJbc4CgCUotmzH/a52MyDECHSDqN3AFRV9r+HWrf+RTlWwaw0c9PL jE76jM6hG1wO1QNs95ZW3xIg0b70qf5USb68Mt6u6fURkF9ouwcg0AcYpKlb9eKAn0Uw L9G2ZR0AwRdzILjG7T+vV5sSNvU3JAVhlFwm9ZsjuS/LimQzPkNpNRJwQFV60J/Yn7Kl YJWuQHyGR4yQHqZDC1OXZ/p8W8qXc5y2XefqMpYGDftOSd7cHxNuAzjqYxEs6s/nkhj7 YGiC4d0lc9cMQWWndPD62xVTOvXTUAg2XMhwWGZS+BvqkvJIqtGVHatjrlB4ThA3y/XG VgGA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:message-id:date:to:from:dkim-signature :delivered-to:arc-authentication-results; bh=YZ0ZQ9w/+ScvcAGs8WsvNDk7sPPdFu8yT4OSTZ0SC14=; b=WuyWjer+esBYT142oTc1jQgeU6NgrzaurfFLTUPxRitm9MI1lC5gzycOcXS5jmHZYW saLKeRBNclr+3ZY3HVw9F2nP9GH/YaS1mm6Yed93yE5hNg9QS/sMj/xLy59CAYxnjWjR jAWjwBqDoxTBuwlxOZBlUhU5iZ7gqTB776v8ci7wYIcLSZzVdCaJD9CKT1WnmO6VKIMu cjFrEGoE29d5egBLwNsvbZptpcKXIxF9LuXwwScESfpj2ytnc8RLdIfZwheU3IOBCXCO HBy9sHZiIBkjfOHGiBhf2tXRGxELnZXe4ly2FHDLJ4LdcIJGvEGZasC7vKEXcDfBctnb y87A== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@tmm1-net.20150623.gappssmtp.com header.s=20150623 header.b=PzUooUOy; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id v3si1098253wmb.117.2018.04.24.19.08.14; Tue, 24 Apr 2018 19:08:15 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@tmm1-net.20150623.gappssmtp.com header.s=20150623 header.b=PzUooUOy; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 96D5568A27D; Wed, 25 Apr 2018 05:07:42 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pf0-f178.google.com (mail-pf0-f178.google.com [209.85.192.178]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 35B45689923 for ; Wed, 25 Apr 2018 05:07:35 +0300 (EEST) Received: by mail-pf0-f178.google.com with SMTP id z9so13848991pfe.6 for ; Tue, 24 Apr 2018 19:08:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tmm1-net.20150623.gappssmtp.com; s=20150623; h=sender:from:to:cc:subject:date:message-id; bh=jEKpOPqZPuT76vDz4sFOczpgv8cGbP8Bzuhz46boMb0=; b=PzUooUOyylD4nnFwPy77op7+oP3BT1xOR0d2m1aD9ilv+YRx62C79b6FHCJOF+7awq NFIsDNRdvPPqH7W09Ofr7QomYlLzujlFMoAOWkEU5DiJGhASyr6sZk1CfoDUvPH2Gjtr xeqfa5z1mnsZV7Wx9rlrqrMaapVjUzcQdYKU/4zUOhv04Ap4JSTKyy8Pc3VBAz/EkkVR Gloj4ZtPKPMC/lEchWJdOZ7AOfqHA9NqF4dSKjdYgCxsx2uRLSp2FOPXEIrzxlW0dasQ FxNIG1FYBz4KHhTuBEkYk8y5lst9flz/Qy2xOB79ccqYVJKvM9wElRyxG9CLoUG+P9nI NmDw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id; bh=jEKpOPqZPuT76vDz4sFOczpgv8cGbP8Bzuhz46boMb0=; b=UjkOSv1QAbkdpc2dtUPDu+ugms1vUkeB1R0WfWMkgZy4q1jYSQVd0GEY0nNP3L2wl1 WysQsStEtoeNh9W1GlxQRJnX0kRdetcF884L3wgeQC4f8od6cALamMXoU6UnF8ReqNEz +2KAprNTtaepfaZTNEqHo21kXVnvW+z3dyjSEsvOH9gICD8wZA+xsxjrSaFJUDLF3rO8 FruIb4sp0l9tfsnhu5ubmTL9jp1jLpVJEg3yWxfZAAm3sqwS5PD6LlFXwtOq4Bbalck4 /l85V/7DWgvxGh5D4DjYMZmUuep4wkcLs7ofa0kxGrfLx39axxlImIvbgeIU8mYJc2mU LfVg== X-Gm-Message-State: ALQs6tBXJLBiJJUSVENYsEnd0fTlJWKOKNMs2tZWvyqso6L5E3ioZxzR 7GqY54ZsZ/en7Fx+7StrKkdMrbYp X-Received: by 10.98.105.2 with SMTP id e2mr26124627pfc.1.1524622082711; Tue, 24 Apr 2018 19:08:02 -0700 (PDT) Received: from localhost.localdomain (c-69-181-54-242.hsd1.ca.comcast.net. [69.181.54.242]) by smtp.gmail.com with ESMTPSA id h185sm5864089pfe.5.2018.04.24.19.08.01 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 24 Apr 2018 19:08:02 -0700 (PDT) From: Aman Gupta To: ffmpeg-devel@ffmpeg.org Date: Tue, 24 Apr 2018 19:07:58 -0700 Message-Id: <20180425020758.91904-1-ffmpeg@tmm1.net> X-Mailer: git-send-email 2.14.2 Subject: [FFmpeg-devel] [PATCH] avcodec: add minimal teletext subtitle decoder X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Aman Gupta , jeebjp@gmail.com MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" From: Aman Gupta Based largely on VLC's modules/codec/telx.c. Processes only teletext pages marked as subtitles, so depending on the stream it might not produce any output. Subtitles are rendered directly to ASS, with support for background colors and a best-effort at screen positioning. The ASS packets are emitted in real time (similar to ccaption_dec's real_time option), with -1 durations. The decoder expects that the player will remove all existing subtitles whenever a new packet arrives. The teletext clear command is implemented using an empty subtitle, which removes existing subtitles but does not render anything new. --- libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/teletextsubdec.c | 522 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 524 insertions(+) create mode 100644 libavcodec/teletextsubdec.c diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 4b8ad121db..83f2eab513 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -593,6 +593,7 @@ OBJS-$(CONFIG_SVQ1_DECODER) += svq1dec.o svq1.o svq13.o h263data.o OBJS-$(CONFIG_SVQ1_ENCODER) += svq1enc.o svq1.o h263data.o \ h263.o ituh263enc.o OBJS-$(CONFIG_SVQ3_DECODER) += svq3.o svq13.o mpegutils.o h264data.o +OBJS-$(CONFIG_TELETEXTSUB_DECODER) += teletextsubdec.o ass.o OBJS-$(CONFIG_TEXT_DECODER) += textdec.o ass.o OBJS-$(CONFIG_TEXT_ENCODER) += srtenc.o ass_split.o OBJS-$(CONFIG_TAK_DECODER) += takdec.o tak.o takdsp.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 4d4ef530e4..f0f23eccef 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -634,6 +634,7 @@ extern AVCodec ff_subrip_encoder; extern AVCodec ff_subrip_decoder; extern AVCodec ff_subviewer_decoder; extern AVCodec ff_subviewer1_decoder; +extern AVCodec ff_teletextsub_decoder; extern AVCodec ff_text_encoder; extern AVCodec ff_text_decoder; extern AVCodec ff_vplayer_decoder; diff --git a/libavcodec/teletextsubdec.c b/libavcodec/teletextsubdec.c new file mode 100644 index 0000000000..f5f0f935ef --- /dev/null +++ b/libavcodec/teletextsubdec.c @@ -0,0 +1,522 @@ +/* + * Minimal Teletext subtitle decoding for ffmpeg + * Copyright (c) 2018 Aman Gupta + * Copyright (c) 2013 Marton Balint + * Copyright (c) 2005-2010, 2012 Wolfram Gloger + * Copyright (c) 2007 Vincent Penne, VLC authors and VideoLAN (modules/codec/telx.c) + * Copyright (c) 2001-2005 dvb.matt, ProjectX java dvb decoder + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include "avcodec.h" +#include "libavcodec/ass.h" +#include "libavcodec/dvbtxt.h" +#include "libavutil/opt.h" +#include "libavutil/bprint.h" +#include "libavutil/internal.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/log.h" +#include "libavutil/reverse.h" + +#define NUM_ROWS 24 +#define ROW_SIZE 40 +#define NUM_MAGAZINES 9 + +typedef struct TeletextContext +{ + AVClass *class; + int64_t pts; + AVBPrint buffer; + int readorder; + + uint8_t rows[NUM_ROWS][ROW_SIZE]; + bool active[NUM_MAGAZINES]; + int charset; +} TeletextContext; + +/* + * My doc only mentions 13 national characters, but experiments show there + * are more, in france for example I already found two more (0x9 and 0xb). + * + * Conversion is in this order : + * + * 0x23 0x24 0x40 0x5b 0x5c 0x5d 0x5e 0x5f 0x60 0x7b 0x7c 0x7d 0x7e + * (these are the standard ones) + * 0x08 0x09 0x0a 0x0b 0x0c 0x0d (apparently a control character) 0x0e 0x0f + */ +static const uint16_t national_charsets[][20] = { + { 0x00a3, 0x0024, 0x0040, 0x00ab, 0x00bd, 0x00bb, 0x005e, 0x0023, + 0x002d, 0x00bc, 0x00a6, 0x00be, 0x00f7 }, /* english ,000 */ + + { 0x00e9, 0x00ef, 0x00e0, 0x00eb, 0x00ea, 0x00f9, 0x00ee, 0x0023, + 0x00e8, 0x00e2, 0x00f4, 0x00fb, 0x00e7, 0, 0x00eb, 0, 0x00ef }, /* french ,001 */ + + { 0x0023, 0x00a4, 0x00c9, 0x00c4, 0x00d6, 0x00c5, 0x00dc, 0x005f, + 0x00e9, 0x00e4, 0x00f6, 0x00e5, 0x00fc }, /* swedish,finnish,hungarian ,010 */ + + { 0x0023, 0x016f, 0x010d, 0x0165, 0x017e, 0x00fd, 0x00ed, 0x0159, + 0x00e9, 0x00e1, 0x011b, 0x00fa, 0x0161 }, /* czech,slovak ,011 */ + + { 0x0023, 0x0024, 0x00a7, 0x00c4, 0x00d6, 0x00dc, 0x005e, 0x005f, + 0x00b0, 0x00e4, 0x00f6, 0x00fc, 0x00df }, /* german ,100 */ + + { 0x00e7, 0x0024, 0x00a1, 0x00e1, 0x00e9, 0x00ed, 0x00f3, 0x00fa, + 0x00bf, 0x00fc, 0x00f1, 0x00e8, 0x00e0 }, /* portuguese,spanish ,101 */ + + { 0x00a3, 0x0024, 0x00e9, 0x00b0, 0x00e7, 0x00bb, 0x005e, 0x0023, + 0x00f9, 0x00e0, 0x00f2, 0x00e8, 0x00ec }, /* italian ,110 */ + + { 0x0023, 0x00a4, 0x0162, 0x00c2, 0x015e, 0x0102, 0x00ce, 0x0131, + 0x0163, 0x00e2, 0x015f, 0x0103, 0x00ee }, /* rumanian ,111 */ + + /* I have these tables too, but I don't know how they can be triggered */ + { 0x0023, 0x0024, 0x0160, 0x0117, 0x0119, 0x017d, 0x010d, 0x016b, + 0x0161, 0x0105, 0x0173, 0x017e, 0x012f }, /* lettish,lithuanian ,1000 */ + + { 0x0023, 0x0144, 0x0105, 0x005a, 0x015a, 0x0141, 0x0107, 0x00f3, + 0x0119, 0x017c, 0x015b, 0x0142, 0x017a }, /* polish, 1001 */ + + { 0x0023, 0x00cb, 0x010c, 0x0106, 0x017d, 0x0110, 0x0160, 0x00eb, + 0x010d, 0x0107, 0x017e, 0x0111, 0x0161 }, /* serbian,croatian,slovenian, 1010 */ + + { 0x0023, 0x00f5, 0x0160, 0x00c4, 0x00d6, 0x017e, 0x00dc, 0x00d5, + 0x0161, 0x00e4, 0x00f6, 0x017e, 0x00fc }, /* estonian ,1011 */ + + { 0x0054, 0x011f, 0x0130, 0x015e, 0x00d6, 0x00c7, 0x00dc, 0x011e, + 0x0131, 0x015f, 0x00f6, 0x00e7, 0x00fc }, /* turkish ,1100 */ +}; + +static const char *color_mappings[8] = { + "{\\c&H000000&}", // black + "{\\c&H0000FF&}", // red + "{\\c&H00FF00&}", // green + "{\\c&H00FFFF&}", // yellow + "{\\c&HFF0000&}", // blue + "{\\c&HFF00FF&}", // magenta + "{\\c&HFFFF00&}", // cyan + "{\\c&HFFFFFF&}" // white +}; + +static int hamming(int a) +{ + switch (a) { + case 0xA8: return 0; + case 0x0B: return 1; + case 0x26: return 2; + case 0x85: return 3; + case 0x92: return 4; + case 0x31: return 5; + case 0x1C: return 6; + case 0xBF: return 7; + case 0x40: return 8; + case 0xE3: return 9; + case 0xCE: return 10; + case 0x6D: return 11; + case 0x7A: return 12; + case 0xD9: return 13; + case 0xF4: return 14; + case 0x57: return 15; + default: return -1; // decoding error , not yet corrected + } +} + +// ucs-2 --> utf-8 +// this is not a general function, but it's enough for what we do here +// the result buffer need to be at least 4 bytes long +static void to_utf8(char *res, uint16_t ch) +{ + if (ch >= 0x80) { + if (ch >= 0x800) { + res[0] = (ch >> 12) | 0xE0; + res[1] = ((ch >> 6) & 0x3F) | 0x80; + res[2] = (ch & 0x3F) | 0x80; + res[3] = 0; + } else { + res[0] = (ch >> 6) | 0xC0; + res[1] = (ch & 0x3F) | 0x80; + res[2] = 0; + } + } else { + res[0] = ch; + res[1] = 0; + } +} + +static void decode_string(AVCodecContext *avctx, AVBPrint *buf, + const uint8_t *packet, int *leading, int *olen) +{ + TeletextContext *ctx = avctx->priv_data; + int i, len = 0; + int end_box = 0; + bool char_seen = false; + char utf8[7]; + const uint16_t *charset = national_charsets[ctx->charset]; + av_bprint_clear(buf); + *leading = 0; + + for (i = 0; i < ROW_SIZE; i++) { + int in = ff_reverse[packet[i]] & 0x7f; + uint16_t out = 32; + + switch (in) { + /* special national characters */ + case 0x23: out = charset[0]; break; + case 0x24: out = charset[1]; break; + case 0x40: out = charset[2]; break; + case 0x5b: out = charset[3]; break; + case 0x5c: out = charset[4]; break; + case 0x5d: out = charset[5]; break; + case 0x5e: out = charset[6]; break; + case 0x5f: out = charset[7]; break; + case 0x60: out = charset[8]; break; + case 0x7b: out = charset[9]; break; + case 0x7c: out = charset[10]; break; + case 0x7d: out = charset[11]; break; + case 0x7e: out = charset[12]; break; + + /* control codes */ + case 0xd: + i++; + in = ff_reverse[packet[i]] & 0x7f; + if (in == 0xb) + in = 7; + if (in >= 0 && in < 8) { + av_bprintf(buf, "%s", color_mappings[in]); + } + continue; + + case 0xa: + end_box++; + if (end_box == 1) + continue; + break; + + case 0xb: + continue; + + /* color codes */ + case 0x0: + case 0x1: + case 0x2: + case 0x3: + case 0x4: + case 0x5: + case 0x6: + case 0x7: + av_bprintf(buf, "%s", color_mappings[in]); + break; + + default: + /* non documented national range 0x08 - 0x0f */ + if (in >= 0x08 && in <= 0x0f) { + out = charset[13 + in - 8]; + break; + } + + /* normal ascii */ + if (in >= 32 && in < 0x7f) + out = in; + } + + if (end_box == 2) + break; + + /* handle undefined national characters */ + if (out == 0) + out = 32; + + if (out == 32 && !char_seen) + (*leading)++; + else if (out != 32) + char_seen = true; + + /* convert to utf-8 */ + to_utf8(utf8, out); + av_bprintf(buf, "%s", utf8); + + if (char_seen || out != 32) + len += strlen(utf8); + } + + /* remove trailing spaces */ + for (i = buf->len-1; i >= 0 && buf->str[i] == 32; i--) + ; + buf->str[i+1] = 0; + buf->len = i; + + *olen = len; +} + +static int teletext_init_decoder(AVCodecContext *avctx) +{ + TeletextContext *ctx = avctx->priv_data; + av_bprint_init(&ctx->buffer, 0, AV_BPRINT_SIZE_UNLIMITED); + ctx->pts = AV_NOPTS_VALUE; + return ff_ass_subtitle_header(avctx, + "Monospace", + ASS_DEFAULT_FONT_SIZE, + ASS_DEFAULT_COLOR, + ASS_DEFAULT_BACK_COLOR, + ASS_DEFAULT_BOLD, + ASS_DEFAULT_ITALIC, + ASS_DEFAULT_UNDERLINE, + 3, + ASS_DEFAULT_ALIGNMENT); +} + +static int teletext_close_decoder(AVCodecContext *avctx) +{ + TeletextContext *ctx = avctx->priv_data; + ctx->pts = AV_NOPTS_VALUE; + av_bprint_finalize(&ctx->buffer, NULL); + return 0; +} + +static void teletext_flush(AVCodecContext *avctx) +{ + TeletextContext *ctx = avctx->priv_data; + int i; + ctx->pts = AV_NOPTS_VALUE; + if (!(avctx->flags2 & AV_CODEC_FLAG2_RO_FLUSH_NOOP)) + ctx->readorder = 0; + for (i = 0; i < NUM_ROWS; i++) + ctx->rows[i][0] = 0; + av_bprint_clear(&ctx->buffer); +} + +static int capture_screen(AVCodecContext *avctx) +{ + TeletextContext *ctx = avctx->priv_data; + int i, j; + int tab = 0; + int num_rows = 0; + bool align_center = false, align_right = false; + bool maybe_center = false; + char prev_line[128] = {0}; + AVBPrint line; + av_bprint_init(&line, 0, AV_BPRINT_SIZE_UNLIMITED); + av_bprint_clear(&ctx->buffer); + + for (i = 0; i < NUM_ROWS; i++) { + int leading, len; + int spaces = 0; + const uint8_t *row = ctx->rows[i]; + if (row[0] == 0) + continue; + num_rows++; + + av_bprint_clear(&line); + decode_string(avctx, &line, row, &leading, &len); + + // av_log(avctx, AV_LOG_DEBUG, "line[%d]: '%s' [leading=%d, len=%d]\n", + // i, line.str, leading, len); + + for (j = 0; j < line.len; j++) { + if (line.str[j] == ' ') + spaces++; + else + break; + } + + if (!tab || spaces < tab) + tab = spaces; + + if (leading > 0 && leading + len > 35) + align_right = true; + else if (leading > 0 && leading*2 + len <= 36) + align_center = true; + else if (leading == 0 && len == 34) + maybe_center = true; + } + + if (num_rows == 1 && maybe_center) + align_center = true; + + for (i = 0; i < NUM_ROWS; i++) { + int leading, len; + int x, y, alignment = 7; + const uint8_t *row = ctx->rows[i]; + bool char_seen = false; + if (row[0] == 0) + continue; + + av_bprint_clear(&line); + decode_string(avctx, &line, row, &leading, &len); + + len = FFMIN(sizeof(prev_line), line.len+1); + if (strncmp(prev_line, line.str, len) == 0) + continue; + strncpy(prev_line, line.str, len); + + /* skip leading space */ + j = 0; + while (line.str[j] == ' ' && j < tab) + j++; + + x = ASS_DEFAULT_PLAYRESX * (0.1 + (0.80 / 34) * j); + y = ASS_DEFAULT_PLAYRESY * (0.1 + (0.80 / 25) * i); + + if (align_center) { + x = ASS_DEFAULT_PLAYRESX * 0.5; + alignment = 8; + } else if (align_right) { + x = ASS_DEFAULT_PLAYRESX * 0.9; + alignment = 9; + } + + av_bprintf(&ctx->buffer, "{\\an%d}{\\pos(%d,%d)}", alignment, x, y); + for (; j <= line.len; j++) { + if (line.str[j] == 32 && !char_seen && !align_center && !align_right) + av_bprintf(&ctx->buffer, "\\h"); + else { + av_bprintf(&ctx->buffer, "%c", line.str[j]); + char_seen = true; + } + } + av_bprintf(&ctx->buffer, "\\N"); + } + + av_bprint_finalize(&line, NULL); + if (!av_bprint_is_complete(&ctx->buffer)) + return AVERROR(ENOMEM); + return 0; +} + +static int teletext_decode_frame(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *pkt) +{ + TeletextContext *ctx = avctx->priv_data; + AVSubtitle *sub = data; + int ret = 0; + int offset = 0; + bool updated = false, erased = false; + + if (avctx->pkt_timebase.num && pkt->pts != AV_NOPTS_VALUE) + ctx->pts = av_rescale_q(pkt->pts, avctx->pkt_timebase, AV_TIME_BASE_Q); + + if (pkt->size) { + const int full_pes_size = pkt->size + 45; /* PES header is 45 bytes */ + + // We allow unreasonably big packets, even if the standard only allows a max size of 1472 + if (full_pes_size < 184 || full_pes_size > 65504 || full_pes_size % 184 != 0) + return AVERROR_INVALIDDATA; + + if (!ff_data_identifier_is_teletext(*pkt->data)) + return pkt->size; + + for (offset = 1; offset + 46 <= pkt->size; offset += 46) { + int mpag, row, magazine; + uint8_t *packet = pkt->data + offset; + if (packet[0] == 0xFF) + continue; + + mpag = (hamming(packet[4]) << 4) | hamming(packet[5]); + if (mpag < 0) + continue; + + row = 0xFF & ff_reverse[mpag]; + magazine = 7 & row; + if (magazine == 0) + magazine = 8; + row >>= 3; + + if (row == 0) { // row 0: flags and header line + int flag = 0; + int i, page, f_charset; + bool f_erase, f_subtitle, f_inhibit, f_suppress, f_update, f_news; + + for (i = 0; i < 6; i++) + flag |= (0xF & (ff_reverse[hamming(packet[8+i])] >> 4)) << (4*i); + + page = (0xF0 & ff_reverse[hamming(packet[7])] ) | + (0x0F & (ff_reverse[hamming(packet[6])] >> 4)); + + f_erase = 1 & (flag>>7); + f_news = 1 & (flag>>15); + f_subtitle = 1 & (flag>>15); + f_suppress = 1 & (flag>>16); + f_update = 1 & (flag>>17); + f_inhibit = 1 & (flag>>19); + f_charset = 7 & (flag>>21); + + ctx->active[magazine] = f_subtitle; + if (!ctx->active[magazine]) + continue; + + ctx->charset = f_charset; + + // av_log(avctx, AV_LOG_DEBUG, "magazine=%d page=%d erase=%d subtitle=%d news=%d charset=%d inhibit=%d suppress=%d update=%d\n", + // magazine, page, f_erase, f_subtitle, f_news, f_charset, f_inhibit, f_suppress, f_update); + + if (f_erase) { + for (i = 0; i < NUM_ROWS; i++) + ctx->rows[i][0] = 0; + erased = true; + continue; + } + + } else if (row < 24) { // row 1-23: normal lines + if (!ctx->active[magazine]) + continue; + if (memcmp(ctx->rows[row], packet+6, ROW_SIZE) != 0) { + updated |= true; + memcpy(ctx->rows[row], packet+6, ROW_SIZE); + } + + // int j; + // av_log(avctx, AV_LOG_DEBUG, "row[%d]: ", row); + // for (j = 0; j < ROW_SIZE; j++) { + // int in = ff_reverse[ctx->rows[row][j]] & 0x7f; + // av_log(avctx, AV_LOG_DEBUG, "0x%x ", in); + // } + // av_log(avctx, AV_LOG_DEBUG, "\n"); + } + } + } + + if (updated || (erased && ctx->buffer.len)) { + ret = capture_screen(avctx); + if (ret < 0) + return ret; + ret = ff_ass_add_rect(sub, ctx->buffer.str, ctx->readorder++, 0, NULL, NULL); + if (ret < 0) + return ret; + sub->end_display_time = -1; + } + + *got_sub = sub->num_rects > 0; + return offset; +} + +static const AVClass teletext_class = { + .class_name = "teletextsub", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVCodec ff_teletextsub_decoder = { + .name = "teletext_subtitle", + .long_name = NULL_IF_CONFIG_SMALL("Minimal DVB teletext subtitle decoder"), + .type = AVMEDIA_TYPE_SUBTITLE, + .id = AV_CODEC_ID_DVB_TELETEXT, + .priv_data_size = sizeof(TeletextContext), + .init = teletext_init_decoder, + .close = teletext_close_decoder, + .decode = teletext_decode_frame, + .capabilities = AV_CODEC_CAP_DELAY, + .flush = teletext_flush, + .priv_class= &teletext_class, +};