mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2026-11-06 05:17:42 +00:00
The CLUT seed is built from every region that may be drawn, so it is the same for every image of a caption, but it was rebuilt for each one. Build it once instead. Converting an image only appends colors past the seed and never changes the alpha table, so restoring the CLUT size at the start of each image restores the seed. The output is unchanged: the encoded bitmaps of both aribcaption FATE samples are identical, with and without ignore_ruby. Reported-by: Forgejo_Fairy Reference: #24992 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb
1226 lines
44 KiB
C
1226 lines
44 KiB
C
/*
|
|
* ARIB STD-B24 caption decoder using the libaribcaption library
|
|
* Copyright (c) 2022 TADANO Tokumei
|
|
*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg 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.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg 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 FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "avcodec.h"
|
|
#include "codec_internal.h"
|
|
#include "internal.h"
|
|
#include "libavcodec/ass.h"
|
|
#include "libavutil/avstring.h"
|
|
#include "libavutil/avutil.h"
|
|
#include "libavutil/mem.h"
|
|
#include "libavutil/thread.h"
|
|
#include "libavutil/log.h"
|
|
#include "libavutil/opt.h"
|
|
|
|
#include <aribcaption/aribcaption.h>
|
|
|
|
#if !defined(DEFAULT_FONT_ASS)
|
|
# define DEFAULT_FONT_ASS "sans-serif"
|
|
#endif
|
|
|
|
#define ARIBC_BPRINT_SIZE_INIT 64
|
|
#define ARIBC_BPRINT_SIZE_MAX (8 * 1024)
|
|
#define ARIBC_ALPHA_MAX_NUM 4
|
|
#define ARIBC_ALPHA_DEFAULT_FRONT 0xFFu
|
|
#define ARIBC_ALPHA_DEFAULT_BACK 0x80u
|
|
|
|
#define ARIBCC_COLOR_RGB(c) ((c) & 0xFFFFFF)
|
|
#define ARIBCC_COLOR_DIFF_RGB(c1,c2) (((c1) ^ (c2)) & 0x00FFFFFF)
|
|
#define ARIBCC_COLOR_DIFF_A(c1,c2) (((c1) ^ (c2)) & 0xFF000000)
|
|
|
|
#define CLUT_RGBA(r,g,b,a) (((unsigned)(a) << 24) | ((r) << 16) | ((g) << 8) | (b))
|
|
#define CLUT_A(c) (((c) >> 24) & 0xFF)
|
|
#define CLUT_R(c) (((c) >> 16) & 0xFF)
|
|
#define CLUT_G(c) (((c) >> 8) & 0xFF)
|
|
#define CLUT_B(c) ( (c) & 0xFF)
|
|
|
|
#define ARIBCC_COLOR_TO_CLUT_RGBA(c,a) (((ARIBCC_COLOR_A(c) ? ARIBCC_COLOR_A(c) : (a)) << 24) | \
|
|
(ARIBCC_COLOR_R(c) << 16) | \
|
|
(ARIBCC_COLOR_G(c) << 8) | \
|
|
(ARIBCC_COLOR_B(c)))
|
|
|
|
typedef struct ARIBCaptionContext {
|
|
AVClass *class;
|
|
AVCodecContext *avctx;
|
|
const AVPacket *avpkt;
|
|
AVSubtitle *sub;
|
|
|
|
aribcc_context_t *context;
|
|
aribcc_decoder_t *decoder;
|
|
aribcc_renderer_t *renderer;
|
|
|
|
int subtitle_type;
|
|
int encoding_scheme;
|
|
int ass_single_rect;
|
|
char *font;
|
|
int force_stroke_text;
|
|
int ignore_background;
|
|
int ignore_ruby;
|
|
float stroke_width;
|
|
int replace_drcs;
|
|
int replace_msz_ascii;
|
|
int replace_msz_japanese;
|
|
int replace_msz_glyph;
|
|
|
|
int64_t pts;
|
|
AVRational time_base;
|
|
int canvas_width;
|
|
int canvas_height;
|
|
int plane_width;
|
|
int plane_height;
|
|
int frame_width;
|
|
int frame_height;
|
|
int bitmap_plane_width;
|
|
int bitmap_plane_height;
|
|
int font_size;
|
|
int charstyle;
|
|
int border_style;
|
|
int readorder;
|
|
|
|
aribcc_caption_t caption;
|
|
aribcc_render_result_t render_result;
|
|
uint32_t *clut;
|
|
int clut_idx;
|
|
int clut_overflow;
|
|
uint8_t clut_alpha[ARIBC_ALPHA_MAX_NUM];
|
|
} ARIBCaptionContext;
|
|
|
|
static void hex_dump_debug(void *ctx, const char *buf, int buf_size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < buf_size; i++) {
|
|
ff_dlog(ctx, "%02hhx ", buf[i]);
|
|
if (i % 16 == 15)
|
|
ff_dlog(ctx, "\n");
|
|
}
|
|
if (i % 16)
|
|
ff_dlog(ctx, "\n");
|
|
}
|
|
|
|
static void logcat_callback(aribcc_loglevel_t level, const char* message, void* userdata)
|
|
{
|
|
ARIBCaptionContext *ctx = userdata;
|
|
int lvl;
|
|
|
|
if (ctx->decoder != NULL) {
|
|
switch (level) {
|
|
case ARIBCC_LOGLEVEL_ERROR:
|
|
lvl = AV_LOG_ERROR;
|
|
break;
|
|
case ARIBCC_LOGLEVEL_WARNING:
|
|
lvl = AV_LOG_WARNING;
|
|
break;
|
|
default:
|
|
lvl = AV_LOG_INFO;
|
|
}
|
|
|
|
av_log(ctx, lvl, "%s\n", message);
|
|
}
|
|
}
|
|
|
|
static void estimate_video_frame_size(ARIBCaptionContext *ctx)
|
|
{
|
|
if (ctx->avctx->width > 0 && ctx->avctx->height > 0) {
|
|
/* input video size specified by -canvas_size option */
|
|
ctx->bitmap_plane_width = ctx->avctx->width;
|
|
ctx->bitmap_plane_height = ctx->avctx->height;
|
|
} else if (ctx->plane_width == 960) {
|
|
/* ARIB TR-B14 Fascicle 2 Volume 3 [Section 2] 4.3.1 */
|
|
/* ARIB TR-B14 Fascicle 2 Volume 3 [Section 2] Appendix-4 */
|
|
ctx->bitmap_plane_width = 1440;
|
|
ctx->bitmap_plane_height = 1080;
|
|
} else {
|
|
ctx->bitmap_plane_width = ctx->plane_width;
|
|
ctx->bitmap_plane_height = ctx->plane_height;
|
|
}
|
|
/* Expand either width or height */
|
|
if (ctx->bitmap_plane_height * ctx->plane_width > ctx->bitmap_plane_width * ctx->plane_height) {
|
|
ctx->frame_height = ctx->bitmap_plane_height;
|
|
ctx->frame_width = ctx->frame_height * ctx->plane_width / ctx->plane_height;
|
|
} else {
|
|
ctx->frame_width = ctx->bitmap_plane_width;
|
|
ctx->frame_height = ctx->frame_width * ctx->plane_height / ctx->plane_width;
|
|
}
|
|
}
|
|
|
|
static void clut_set_alpha(ARIBCaptionContext *ctx, uint8_t a)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARIBC_ALPHA_MAX_NUM; i++) {
|
|
if (ctx->clut_alpha[i] == 0) {
|
|
ctx->clut_alpha[i] = a;
|
|
return;
|
|
}
|
|
if (ctx->clut_alpha[i] == a)
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static uint8_t clut_find_nearlest_alpha(ARIBCaptionContext *ctx, uint8_t a)
|
|
{
|
|
int i, j, d;
|
|
|
|
if (a == 0)
|
|
return a;
|
|
d = 256;
|
|
j = 0;
|
|
for (i = 0; i < ARIBC_ALPHA_MAX_NUM; i++) {
|
|
if (ctx->clut_alpha[i] == a)
|
|
return a;
|
|
if (ctx->clut_alpha[i] == 0)
|
|
break;
|
|
if (abs((int)a - (int)ctx->clut_alpha[i]) < d) {
|
|
d = abs((int)a - (int)ctx->clut_alpha[i]);
|
|
j = i;
|
|
}
|
|
}
|
|
return ctx->clut_alpha[j];
|
|
}
|
|
|
|
static int clut_find(ARIBCaptionContext *ctx, uint32_t rgba)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ctx->clut_idx; i++) {
|
|
if (ctx->clut[i] == rgba)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static inline int clut_color_distance(uint32_t rgba1, uint32_t rgba2)
|
|
{
|
|
return abs((int)CLUT_R(rgba1) - (int)CLUT_R(rgba2)) +
|
|
abs((int)CLUT_G(rgba1) - (int)CLUT_G(rgba2)) +
|
|
abs((int)CLUT_B(rgba1) - (int)CLUT_B(rgba2));
|
|
}
|
|
|
|
static uint8_t clut_pick_or_set(ARIBCaptionContext *ctx, int r, int g, int b, int a)
|
|
{
|
|
int c, i, d, d_min;
|
|
uint32_t rgba;
|
|
|
|
a = clut_find_nearlest_alpha(ctx, a);
|
|
if (a == 0)
|
|
return 0; /* transparent */
|
|
rgba = CLUT_RGBA(r,g,b,a);
|
|
|
|
d_min = 256 * 3;
|
|
c = 0;
|
|
for (i = 0; i < ctx->clut_idx; i++) {
|
|
if (ctx->clut[i] == rgba)
|
|
return i;
|
|
if (CLUT_A(ctx->clut[i]) != a)
|
|
continue;
|
|
d = clut_color_distance(ctx->clut[i], rgba);
|
|
if (d < d_min) {
|
|
d_min = d;
|
|
c = i;
|
|
}
|
|
}
|
|
if (d_min > 3) {
|
|
if (ctx->clut_idx >= AVPALETTE_COUNT)
|
|
ctx->clut_overflow++;
|
|
else {
|
|
c = ctx->clut_idx;
|
|
ctx->clut[ctx->clut_idx++] = rgba;
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
/* initialize CLUT with only the transparent color */
|
|
static void clut_reset(ARIBCaptionContext *ctx)
|
|
{
|
|
ctx->clut[0] = CLUT_RGBA(0,0,0,0); /* transparent */
|
|
memset(ctx->clut_alpha, 0, sizeof(ctx->clut_alpha));
|
|
ctx->clut_alpha[0] = 0xFF;
|
|
ctx->clut_idx = 1;
|
|
ctx->clut_overflow = 0;
|
|
}
|
|
|
|
/* add a color to the CLUT unless it is already there or the CLUT is full */
|
|
static void clut_add(ARIBCaptionContext *ctx, uint32_t rgba)
|
|
{
|
|
if (clut_find(ctx, rgba) >= 0)
|
|
return;
|
|
if (ctx->clut_idx >= AVPALETTE_COUNT) {
|
|
ctx->clut_overflow++;
|
|
return;
|
|
}
|
|
ctx->clut[ctx->clut_idx++] = rgba;
|
|
clut_set_alpha(ctx, CLUT_A(rgba));
|
|
}
|
|
|
|
/* whether a region is left out: no character, or ruby when ruby is ignored */
|
|
static int region_left_out(const aribcc_caption_region_t *region, int ignore_ruby)
|
|
{
|
|
return !region->char_count || (region->is_ruby && ignore_ruby);
|
|
}
|
|
|
|
static int region_skipped(const ARIBCaptionContext *ctx,
|
|
const aribcc_caption_region_t *region)
|
|
{
|
|
return region_left_out(region, ctx->ignore_ruby);
|
|
}
|
|
|
|
/* add the character colors of a region to the CLUT */
|
|
static void clut_add_region(ARIBCaptionContext *ctx, aribcc_caption_region_t *region)
|
|
{
|
|
for (int i = 0; i < region->char_count; i++) {
|
|
aribcc_caption_char_t *ch = ®ion->chars[i];
|
|
clut_add(ctx, ARIBCC_COLOR_TO_CLUT_RGBA(ch->text_color, ARIBC_ALPHA_DEFAULT_FRONT));
|
|
clut_add(ctx, ARIBCC_COLOR_TO_CLUT_RGBA(ch->back_color, ARIBC_ALPHA_DEFAULT_BACK));
|
|
clut_add(ctx, ARIBCC_COLOR_TO_CLUT_RGBA(ch->stroke_color, ARIBC_ALPHA_DEFAULT_FRONT));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* aribcaption_trans_{bitmap|ass|text}_subtitle()
|
|
*
|
|
* Transfer decoded subtitle to AVSubtitle with corresponding subtitle type.
|
|
*
|
|
* @param ctx pointer to the ARIBCaptionContext
|
|
* @return > 0 subtitle event produced (possibly with zero rectangles for clear)
|
|
* = 0 no subtitle
|
|
* < 0 error code
|
|
*/
|
|
static int aribcaption_trans_bitmap_subtitle(ARIBCaptionContext *ctx)
|
|
{
|
|
int ret = 0;
|
|
AVSubtitle *sub = ctx->sub;
|
|
int status, rect_idx, image_count = 0, seed_idx, seed_overflow;
|
|
int old_width = ctx->frame_width;
|
|
int old_height = ctx->frame_height;
|
|
|
|
if (ctx->caption.plane_width > 0 && ctx->caption.plane_height > 0) {
|
|
ctx->plane_width = ctx->caption.plane_width;
|
|
ctx->plane_height = ctx->caption.plane_height;
|
|
}
|
|
estimate_video_frame_size(ctx);
|
|
if (ctx->frame_width != old_width || ctx->frame_height != old_height) {
|
|
ff_dlog(ctx, "canvas: %dx%d plane: %dx%d bitmap: %dx%d frame: %dx%d\n",
|
|
ctx->avctx->width, ctx->avctx->height,
|
|
ctx->plane_width, ctx->plane_height,
|
|
ctx->bitmap_plane_width, ctx->bitmap_plane_height,
|
|
ctx->frame_width, ctx->frame_height);
|
|
if (!aribcc_renderer_set_frame_size(ctx->renderer,
|
|
ctx->frame_width, ctx->frame_height)) {
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"aribcc_renderer_set_frame_size() returned with error.\n");
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
}
|
|
|
|
status = aribcc_renderer_append_caption(ctx->renderer, &ctx->caption);
|
|
if (!status) {
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"aribcc_renderer_append_caption() returned with error.\n");
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
|
|
status = aribcc_renderer_render(ctx->renderer, ctx->pts, &ctx->render_result);
|
|
switch (status) {
|
|
case ARIBCC_RENDER_STATUS_GOT_IMAGE:
|
|
break;
|
|
|
|
case ARIBCC_RENDER_STATUS_GOT_IMAGE_UNCHANGED:
|
|
aribcc_render_result_cleanup(&ctx->render_result);
|
|
ff_dlog(ctx, "got image unchanged\n");
|
|
return 0;
|
|
|
|
case ARIBCC_RENDER_STATUS_NO_IMAGE:
|
|
/* the result holds no image, handled below */
|
|
break;
|
|
|
|
case ARIBCC_RENDER_STATUS_ERROR:
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"aribcc_renderer_render() returned with error.\n");
|
|
return AVERROR_EXTERNAL;
|
|
|
|
default:
|
|
aribcc_render_result_cleanup(&ctx->render_result);
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"aribcc_renderer_render() returned unknown status: %d\n", status);
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
|
|
if (!ctx->render_result.image_count || ctx->render_result.images == NULL) {
|
|
ff_dlog(ctx, "no image (%d)\n", ctx->render_result.image_count);
|
|
aribcc_render_result_cleanup(&ctx->render_result);
|
|
/* A caption carrying an explicit clear screen (CS) may produce no
|
|
image. Report an empty subtitle so that the previously displayed
|
|
bitmap is erased; the TEXT path already does this by emitting "".
|
|
Do not treat every caption without image as a clear: a statement
|
|
containing only TIME also decodes to zero regions but must not
|
|
erase the display. */
|
|
if (ctx->caption.flags & ARIBCC_CAPTIONFLAGS_CLEARSCREEN) {
|
|
sub->format = 0; /* graphic */
|
|
sub->num_rects = 0;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
image_count = ctx->render_result.image_count;
|
|
sub->format = 0; /* graphic */
|
|
sub->rects = av_calloc(image_count, sizeof(*sub->rects));
|
|
if (!sub->rects) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
for (int i = 0; i < image_count; i++) {
|
|
sub->rects[i] = av_mallocz(sizeof(*sub->rects[i]));
|
|
if (!sub->rects[i]) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
/* The renderer drops ruby regions with ignore_ruby and regions too small
|
|
to draw, without telling which region an image comes from. Seed the
|
|
CLUT with the colors of every region that may be drawn, once. Each
|
|
image only appends colors past the seed, so dropping them restores it. */
|
|
clut_reset(ctx);
|
|
for (int i = 0; i < ctx->caption.region_count; i++)
|
|
if (!region_skipped(ctx, &ctx->caption.regions[i]))
|
|
clut_add_region(ctx, &ctx->caption.regions[i]);
|
|
seed_idx = ctx->clut_idx;
|
|
seed_overflow = ctx->clut_overflow;
|
|
|
|
for (rect_idx = 0; rect_idx < image_count; rect_idx++) {
|
|
AVSubtitleRect *rect = sub->rects[rect_idx];
|
|
aribcc_image_t *image = &ctx->render_result.images[rect_idx];
|
|
int w, h, shrink_height, dst_idx;
|
|
|
|
ctx->clut_idx = seed_idx;
|
|
ctx->clut_overflow = seed_overflow;
|
|
|
|
rect->w = image->width * ctx->bitmap_plane_width / ctx->frame_width;
|
|
rect->h = image->height * ctx->bitmap_plane_height / ctx->frame_height;
|
|
rect->data[0] = av_mallocz(rect->w * rect->h);
|
|
if (!rect->data[0]) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
if ((image->height != rect->h && image->width != rect->w) ||
|
|
image->stride < image->width * 4 ||
|
|
image->stride * image->height > image->bitmap_size) {
|
|
av_log(ctx, AV_LOG_ERROR, "Bug: unexpected rendered image: %d(%d)x%d -> %dx%d\n",
|
|
image->width, image->stride / 4, image->height, rect->w, rect->h);
|
|
ret = AVERROR_EXTERNAL;
|
|
goto fail;
|
|
}
|
|
|
|
shrink_height = image->height != rect->h;
|
|
dst_idx = 0;
|
|
for (h = 0; h < rect->h; h++) {
|
|
for (w = 0; w < rect->w; w++) {
|
|
/* Bi-linear interpolation */
|
|
int n, m, idx0, idx1, r, g, b, a;
|
|
if (shrink_height) {
|
|
int div_a, y0, y1;
|
|
div_a = h * ctx->frame_height;
|
|
n = ctx->bitmap_plane_height;
|
|
y0 = div_a / n;
|
|
y1 = FFMIN(y0 + 1, image->height - 1);
|
|
m = div_a - n * y0;
|
|
idx0 = image->stride * y0 + w * 4;
|
|
idx1 = image->stride * y1 + w * 4;
|
|
} else {
|
|
int div_a, x0, x1;
|
|
div_a = w * ctx->frame_width;
|
|
n = ctx->bitmap_plane_width;
|
|
x0 = div_a / n;
|
|
x1 = FFMIN(x0 + 1, image->width - 1);
|
|
m = div_a - n * x0;
|
|
idx0 = image->stride * h + x0 * 4;
|
|
idx1 = image->stride * h + x1 * 4;
|
|
}
|
|
r = (image->bitmap[idx0++] * (n - m) + image->bitmap[idx1++] * m) / n;
|
|
g = (image->bitmap[idx0++] * (n - m) + image->bitmap[idx1++] * m) / n;
|
|
b = (image->bitmap[idx0++] * (n - m) + image->bitmap[idx1++] * m) / n;
|
|
a = (image->bitmap[idx0++] * (n - m) + image->bitmap[idx1++] * m) / n;
|
|
rect->data[0][dst_idx++] = clut_pick_or_set(ctx, r, g, b, a);
|
|
}
|
|
}
|
|
rect->data[1] = av_memdup(ctx->clut, AVPALETTE_SIZE);
|
|
if (!rect->data[1]) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
if (ctx->avctx->profile == AV_PROFILE_ARIB_PROFILE_C) {
|
|
/* ARIB TR-B14 version 3.8 Fascicle 1-(2/2) Volume 3 [Section 4] */
|
|
/* No position information is provided for profile C */
|
|
rect->x = (ctx->frame_width - rect->w) / 2;
|
|
rect->y = ctx->frame_height - rect->h * (image_count - rect_idx);
|
|
} else {
|
|
rect->x = image->dst_x * ctx->bitmap_plane_width / ctx->frame_width;
|
|
rect->y = image->dst_y * ctx->bitmap_plane_height / ctx->frame_height;
|
|
}
|
|
rect->type = SUBTITLE_BITMAP;
|
|
rect->linesize[0] = rect->w;
|
|
rect->nb_colors = 256;
|
|
|
|
ff_dlog(ctx, "BITMAP subtitle (%d,%d) %dx%d -> (%d,%d) %dx%d [%d]: %d colors\n",
|
|
image->dst_x, image->dst_y, image->width, image->height,
|
|
rect->x, rect->y, rect->w, rect->h,
|
|
rect_idx, ctx->clut_idx);
|
|
if (ctx->clut_overflow)
|
|
av_log(ctx, AV_LOG_WARNING, "CLUT overflow (%d).\n", ctx->clut_overflow);
|
|
}
|
|
sub->num_rects = rect_idx;
|
|
aribcc_render_result_cleanup(&ctx->render_result);
|
|
|
|
return rect_idx;
|
|
|
|
fail:
|
|
if (sub->rects) {
|
|
for (int i = 0; i < image_count; i++) {
|
|
if (sub->rects[i]) {
|
|
av_freep(&sub->rects[i]->data[0]);
|
|
av_freep(&sub->rects[i]->data[1]);
|
|
av_freep(&sub->rects[i]);
|
|
}
|
|
}
|
|
av_freep(&sub->rects);
|
|
}
|
|
sub->num_rects = 0;
|
|
aribcc_render_result_cleanup(&ctx->render_result);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int set_ass_header(ARIBCaptionContext *ctx)
|
|
{
|
|
AVCodecContext *avctx = ctx->avctx;
|
|
int outline, shadow;
|
|
const char *font_name;
|
|
const char *fonts = ctx->font;
|
|
|
|
if (ctx->border_style == 4) {
|
|
outline = 0;
|
|
shadow = 4;
|
|
} else {
|
|
outline = 1;
|
|
shadow = 0;
|
|
}
|
|
if (ctx->force_stroke_text)
|
|
outline = (int)(ctx->stroke_width * 4.0 / 3.0);
|
|
|
|
if (fonts && *fonts)
|
|
font_name = av_get_token(&fonts, ",");
|
|
else
|
|
font_name = av_strdup(DEFAULT_FONT_ASS);
|
|
if (!font_name)
|
|
return AVERROR(ENOMEM);
|
|
|
|
av_freep(&avctx->subtitle_header);
|
|
avctx->subtitle_header = av_asprintf(
|
|
"[Script Info]\n"
|
|
"ScriptType: v4.00+\n"
|
|
"PlayResX: %d\n"
|
|
"PlayResY: %d\n"
|
|
"WrapStyle: 2\n" /* 2: no word wrapping */
|
|
"\n"
|
|
|
|
"[V4+ Styles]\n"
|
|
"Format: Name, "
|
|
"Fontname, Fontsize, "
|
|
"PrimaryColour, SecondaryColour, OutlineColour, BackColour, "
|
|
"Bold, Italic, Underline, StrikeOut, "
|
|
"ScaleX, ScaleY, "
|
|
"Spacing, Angle, "
|
|
"BorderStyle, Outline, Shadow, "
|
|
"Alignment, MarginL, MarginR, MarginV, "
|
|
"Encoding\n"
|
|
|
|
"Style: "
|
|
"Default," /* Name */
|
|
"%s,%d," /* Font{name,size} */
|
|
"&H%x,&H%x,&H%x,&H%x," /* {Primary,Secondary,Outline,Back}Colour */
|
|
"%d,%d,%d,0," /* Bold, Italic, Underline, StrikeOut */
|
|
"100,100," /* Scale{X,Y} */
|
|
"0,0," /* Spacing, Angle */
|
|
"%d,%d,%d," /* BorderStyle, Outline, Shadow */
|
|
"%d,10,10,10," /* Alignment, Margin[LRV] */
|
|
"0\n" /* Encoding */
|
|
"\n"
|
|
|
|
"[Events]\n"
|
|
"Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n",
|
|
ctx->plane_width, ctx->plane_height,
|
|
font_name, ctx->font_size,
|
|
ASS_DEFAULT_COLOR, ASS_DEFAULT_COLOR,
|
|
ASS_DEFAULT_BACK_COLOR, ASS_DEFAULT_BACK_COLOR,
|
|
-ASS_DEFAULT_BOLD, -ASS_DEFAULT_ITALIC, -ASS_DEFAULT_UNDERLINE,
|
|
ctx->border_style, outline, shadow, ASS_DEFAULT_ALIGNMENT);
|
|
|
|
av_freep(&font_name);
|
|
if (!avctx->subtitle_header)
|
|
return AVERROR(ENOMEM);
|
|
avctx->subtitle_header_size = strlen(avctx->subtitle_header);
|
|
return 0;
|
|
}
|
|
|
|
static void set_ass_color(AVBPrint *buf, int color_num,
|
|
aribcc_color_t new_color, aribcc_color_t old_color)
|
|
{
|
|
if (ARIBCC_COLOR_DIFF_RGB(new_color, old_color))
|
|
av_bprintf(buf, "{\\%dc&H%06x&}", color_num,
|
|
ARIBCC_COLOR_RGB(new_color));
|
|
if (ARIBCC_COLOR_DIFF_A(new_color, old_color))
|
|
av_bprintf(buf, "{\\%da&H%02x&}", color_num,
|
|
0xFF - ARIBCC_COLOR_A(new_color));
|
|
}
|
|
|
|
/* position of a region, keeping the existing wrapping of negative values
|
|
in ass mode */
|
|
static void region_position(const ARIBCaptionContext *ctx,
|
|
const aribcc_caption_region_t *region, int *x, int *y)
|
|
{
|
|
*x = region->x < 0 ? region->x + ctx->plane_width : region->x;
|
|
*y = region->y < 0 ? region->y + ctx->plane_height : region->y;
|
|
}
|
|
|
|
static int aribcaption_trans_ass_subtitle(ARIBCaptionContext *ctx)
|
|
{
|
|
AVSubtitle *sub = ctx->sub;
|
|
AVBPrint buf;
|
|
bool single_rect = ctx->ass_single_rect;
|
|
bool emitted = false;
|
|
int ret = 0, rect_idx;
|
|
|
|
if (ctx->caption.plane_width > 0 && ctx->caption.plane_height > 0 &&
|
|
(ctx->caption.plane_width != ctx->plane_width ||
|
|
ctx->caption.plane_height != ctx->plane_height)) {
|
|
ctx->plane_width = ctx->caption.plane_width;
|
|
ctx->plane_height = ctx->caption.plane_height;
|
|
if ((ret = set_ass_header(ctx)) < 0)
|
|
return ret;
|
|
}
|
|
|
|
/* ARIB TR-B14 version 3.8 Fascicle 1-(2/2) Volume 3 [Section 4] */
|
|
/* No position information is provided for profile C */
|
|
if (ctx->avctx->profile == AV_PROFILE_ARIB_PROFILE_C)
|
|
single_rect = true;
|
|
|
|
sub->format = 1; /* text */
|
|
|
|
av_bprint_init(&buf, ARIBC_BPRINT_SIZE_INIT, ARIBC_BPRINT_SIZE_MAX);
|
|
|
|
if (single_rect && ctx->avctx->profile != AV_PROFILE_ARIB_PROFILE_C) {
|
|
int x, y, rx, ry;
|
|
x = ctx->plane_width;
|
|
y = ctx->plane_height;
|
|
for (int i = 0; i < ctx->caption.region_count; i++) {
|
|
if (region_skipped(ctx, &ctx->caption.regions[i]))
|
|
continue;
|
|
region_position(ctx, &ctx->caption.regions[i], &rx, &ry);
|
|
if (rx < x)
|
|
x = rx;
|
|
if (ry < y)
|
|
y = ry;
|
|
}
|
|
av_bprintf(&buf, "{\\an7}");
|
|
if (x > 0 || y > 0)
|
|
av_bprintf(&buf, "{\\pos(%d,%d)}", x, y);
|
|
}
|
|
|
|
rect_idx = 0;
|
|
for (int i = 0; i < ctx->caption.region_count; i++) {
|
|
aribcc_caption_region_t *region = &ctx->caption.regions[i];
|
|
aribcc_color_t text_color = ARIBCC_MAKE_RGBA(0xFF, 0xFF, 0xFF,
|
|
ARIBC_ALPHA_DEFAULT_FRONT);
|
|
aribcc_color_t stroke_color = ARIBCC_MAKE_RGBA(0, 0, 0,
|
|
ARIBC_ALPHA_DEFAULT_FRONT);
|
|
aribcc_color_t back_color = ARIBCC_MAKE_RGBA(0, 0, 0,
|
|
ARIBC_ALPHA_DEFAULT_BACK);
|
|
aribcc_charstyle_t charstyle = ctx->charstyle;
|
|
int char_width = ctx->font_size;
|
|
int char_height = ctx->font_size;
|
|
int char_horizontal_spacing = 0;
|
|
|
|
if (region_skipped(ctx, region))
|
|
continue;
|
|
|
|
if (single_rect && emitted)
|
|
av_bprintf(&buf, "{\\r}\\N");
|
|
emitted = true;
|
|
|
|
if (!single_rect) {
|
|
int x, y;
|
|
region_position(ctx, region, &x, &y);
|
|
av_bprint_clear(&buf);
|
|
av_bprintf(&buf, "{\\an7}");
|
|
if (x > 0 || y > 0)
|
|
av_bprintf(&buf, "{\\pos(%d,%d)}", x, y);
|
|
}
|
|
if (region->is_ruby)
|
|
av_bprintf(&buf, "{\\fs%d}", char_height / 2);
|
|
|
|
for (int j = 0; j < region->char_count; j++) {
|
|
aribcc_caption_char_t *ch = ®ion->chars[j];
|
|
|
|
if (ctx->avctx->profile != AV_PROFILE_ARIB_PROFILE_C) {
|
|
if (ch->char_horizontal_spacing != char_horizontal_spacing) {
|
|
av_bprintf(&buf, "{\\fsp%d}", (region->is_ruby) ?
|
|
ch->char_horizontal_spacing / 2 :
|
|
ch->char_horizontal_spacing);
|
|
char_horizontal_spacing = ch->char_horizontal_spacing;
|
|
}
|
|
if (ch->char_width != char_width) {
|
|
av_bprintf(&buf, "{\\fscx%"PRId64"}",
|
|
av_rescale(ch->char_width, 100, ctx->font_size));
|
|
char_width = ch->char_width;
|
|
}
|
|
if (ch->char_height != char_height) {
|
|
av_bprintf(&buf, "{\\fscy%"PRId64"}",
|
|
av_rescale(ch->char_height, 100, ctx->font_size));
|
|
char_height = ch->char_height;
|
|
}
|
|
}
|
|
if (ch->style != charstyle) {
|
|
aribcc_charstyle_t diff = ch->style ^ charstyle;
|
|
if (diff & ARIBCC_CHARSTYLE_STROKE) {
|
|
if (charstyle & ARIBCC_CHARSTYLE_STROKE) {
|
|
if (ctx->force_stroke_text)
|
|
av_bprintf(&buf, "{\\bord%d}",
|
|
(int)(ctx->stroke_width * 4.0 / 3.0));
|
|
else
|
|
av_bprintf(&buf, "{\\bord0}");
|
|
} else
|
|
av_bprintf(&buf, "{\\bord3}");
|
|
}
|
|
if (diff & ARIBCC_CHARSTYLE_BOLD) {
|
|
if (charstyle & ARIBCC_CHARSTYLE_BOLD)
|
|
av_bprintf(&buf, "{\\b0}");
|
|
else
|
|
av_bprintf(&buf, "{\\b1}");
|
|
}
|
|
if (diff & ARIBCC_CHARSTYLE_ITALIC) {
|
|
if (charstyle & ARIBCC_CHARSTYLE_ITALIC)
|
|
av_bprintf(&buf, "{\\i0}");
|
|
else
|
|
av_bprintf(&buf, "{\\i1}");
|
|
}
|
|
if (diff & ARIBCC_CHARSTYLE_UNDERLINE) {
|
|
if (charstyle & ARIBCC_CHARSTYLE_UNDERLINE)
|
|
av_bprintf(&buf, "{\\u0}");
|
|
else
|
|
av_bprintf(&buf, "{\\u1}");
|
|
}
|
|
charstyle = ch->style;
|
|
}
|
|
if (ch->text_color != text_color) {
|
|
set_ass_color(&buf, 1, ch->text_color, text_color);
|
|
text_color = ch->text_color;
|
|
}
|
|
if (ch->stroke_color != stroke_color) {
|
|
set_ass_color(&buf, 3, ch->stroke_color, stroke_color);
|
|
stroke_color = ch->stroke_color;
|
|
}
|
|
if (ch->back_color != back_color) {
|
|
if (ctx->border_style == 4)
|
|
set_ass_color(&buf, 4, ch->back_color, back_color);
|
|
else
|
|
set_ass_color(&buf, 3, ch->back_color, back_color);
|
|
back_color = ch->back_color;
|
|
}
|
|
if (region->chars[j].type == ARIBCC_CHARTYPE_DRCS)
|
|
av_bprintf(&buf, "\xe3\x80\x93"); /* Geta Mark */
|
|
else
|
|
ff_ass_bprint_text_event(&buf, ch->u8str, strlen(ch->u8str), "", 0);
|
|
}
|
|
|
|
if (single_rect) {
|
|
ff_dlog(ctx, "ASS subtitle%s (%d,%d) %dx%d [region %d]\n",
|
|
(region->is_ruby) ? " (ruby)" : "",
|
|
region->x, region->y, region->width, region->height, i);
|
|
} else {
|
|
if (!av_bprint_is_complete(&buf)) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
ff_dlog(ctx, "ASS subtitle%s (%d,%d) %dx%d [%d]: %s\n",
|
|
(region->is_ruby) ? " (ruby)" : "",
|
|
region->x, region->y, region->width, region->height,
|
|
rect_idx, buf.str);
|
|
|
|
ret = ff_ass_add_rect(sub, buf.str, ctx->readorder++, 0 , NULL, NULL);
|
|
if (ret != 0)
|
|
goto fail;
|
|
rect_idx++;
|
|
}
|
|
}
|
|
if (!emitted) {
|
|
/* Nothing to show: there is no region, or every region was skipped
|
|
as ruby. Only an explicit clear screen (CS) erases the previous
|
|
caption, for indefinite duration; a statement containing only
|
|
TIME also has no region. */
|
|
if (!(ctx->caption.flags & ARIBCC_CAPTIONFLAGS_CLEARSCREEN)) {
|
|
av_bprint_finalize(&buf, NULL);
|
|
return 0;
|
|
}
|
|
ret = ff_ass_add_rect(sub, "", ctx->readorder++, 0, NULL, NULL);
|
|
if (ret < 0)
|
|
goto fail;
|
|
av_bprint_finalize(&buf, NULL);
|
|
return 1;
|
|
}
|
|
if (single_rect) {
|
|
if (!av_bprint_is_complete(&buf)) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
ff_dlog(ctx, "ASS subtitle: %s\n", buf.str);
|
|
|
|
ret = ff_ass_add_rect(sub, buf.str, ctx->readorder++, 0 , NULL, NULL);
|
|
if (ret != 0)
|
|
goto fail;
|
|
rect_idx++;
|
|
}
|
|
|
|
av_bprint_finalize(&buf, NULL);
|
|
return rect_idx;
|
|
|
|
fail:
|
|
if (sub->rects) {
|
|
for (int i = 0; i < sub->num_rects; i++) {
|
|
if (sub->rects[i]) {
|
|
av_freep(&sub->rects[i]->ass);
|
|
av_freep(&sub->rects[i]);
|
|
}
|
|
}
|
|
av_freep(&sub->rects);
|
|
}
|
|
sub->num_rects = 0;
|
|
av_bprint_finalize(&buf, NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aribcaption_trans_text_subtitle(ARIBCaptionContext *ctx)
|
|
{
|
|
AVSubtitle *sub = ctx->sub;
|
|
AVSubtitleRect *rect;
|
|
int ret = 0, has_text = 0;
|
|
const char *text = ctx->caption.text ? ctx->caption.text : "";
|
|
|
|
/* The text leaves ruby out (except a placeholder for an unknown DRCS)
|
|
and may hold line breaks without any character, so decide on the
|
|
regions. */
|
|
for (int i = 0; i < ctx->caption.region_count && !has_text; i++)
|
|
has_text = !region_left_out(&ctx->caption.regions[i], 1);
|
|
|
|
if (!has_text) {
|
|
/* Nothing to show: there is no region, or only ruby. Only an
|
|
explicit clear screen (CS) erases the previous caption, with an
|
|
empty text; a statement containing only TIME also has no region. */
|
|
if (!(ctx->caption.flags & ARIBCC_CAPTIONFLAGS_CLEARSCREEN))
|
|
return 0;
|
|
text = "";
|
|
}
|
|
|
|
sub->rects = av_calloc(1, sizeof(*sub->rects));
|
|
if (!sub->rects) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
sub->num_rects = 1;
|
|
|
|
sub->rects[0] = av_mallocz(sizeof(*sub->rects[0]));
|
|
if (!sub->rects[0]) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
rect = sub->rects[0];
|
|
|
|
ff_dlog(ctx, "TEXT subtitle: %s\n", text);
|
|
rect->text = av_strdup(text);
|
|
if (!rect->text) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
sub->format = 1; /* text */
|
|
rect->type = SUBTITLE_TEXT;
|
|
|
|
return 1;
|
|
|
|
fail:
|
|
if (sub->rects) {
|
|
rect = sub->rects[0];
|
|
if (rect) {
|
|
av_freep(&rect->text);
|
|
av_freep(&rect);
|
|
}
|
|
av_freep(&sub->rects);
|
|
}
|
|
sub->num_rects = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aribcaption_decode(AVCodecContext *avctx, AVSubtitle *sub,
|
|
int *got_sub_ptr, const AVPacket *avpkt)
|
|
{
|
|
ARIBCaptionContext *ctx = avctx->priv_data;
|
|
int status;
|
|
|
|
ff_dlog(ctx, "ARIB caption packet pts=%"PRIx64":\n", avpkt->pts);
|
|
if (sub->num_rects) {
|
|
avpriv_request_sample(ctx, "Different Version of Segment asked Twice");
|
|
return AVERROR_PATCHWELCOME;
|
|
}
|
|
hex_dump_debug(ctx, avpkt->data, avpkt->size);
|
|
|
|
ctx->sub = sub;
|
|
ctx->avpkt = avpkt;
|
|
ctx->time_base = avctx->pkt_timebase;
|
|
if (ctx->time_base.num <= 0 || ctx->time_base.den <= 0) {
|
|
av_log(ctx, AV_LOG_VERBOSE, "No timebase set. assuming 90kHz.\n");
|
|
ctx->time_base = av_make_q(1, 90000);
|
|
}
|
|
if (avpkt->pts == AV_NOPTS_VALUE)
|
|
ctx->pts = ARIBCC_PTS_NOPTS;
|
|
else
|
|
ctx->pts = av_rescale_q(avpkt->pts, ctx->time_base, (AVRational){1, 1000});
|
|
|
|
status = aribcc_decoder_decode(ctx->decoder, avpkt->data, avpkt->size,
|
|
ctx->pts, &ctx->caption);
|
|
if (status == ARIBCC_DECODE_STATUS_ERROR) {
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"aribcc_decoder_decode() returned with error.\n");
|
|
return AVERROR(EAGAIN);
|
|
}
|
|
if (status == ARIBCC_DECODE_STATUS_NO_CAPTION) {
|
|
ff_dlog(ctx, "No caption.\n");
|
|
return avpkt->size;
|
|
} else {
|
|
ff_dlog(ctx, "type=%02x, flags=%x, lang=%03x\n",
|
|
ctx->caption.type, ctx->caption.flags, ctx->caption.iso6392_language_code);
|
|
ff_dlog(ctx, "region count = %d, start=%d.%d, duration=%d.%d\n",
|
|
ctx->caption.region_count,
|
|
(int)(ctx->caption.pts / 1000), (int)(ctx->caption.pts % 1000),
|
|
(int)((ctx->caption.wait_duration == ARIBCC_DURATION_INDEFINITE) ?
|
|
-1 : ctx->caption.wait_duration / 1000),
|
|
(int)((ctx->caption.wait_duration == ARIBCC_DURATION_INDEFINITE) ?
|
|
0 : ctx->caption.wait_duration % 1000));
|
|
}
|
|
|
|
switch ((enum AVSubtitleType) ctx->subtitle_type) {
|
|
case SUBTITLE_TEXT:
|
|
status = aribcaption_trans_text_subtitle(ctx);
|
|
break;
|
|
|
|
case SUBTITLE_ASS:
|
|
status = aribcaption_trans_ass_subtitle(ctx);
|
|
break;
|
|
|
|
case SUBTITLE_BITMAP:
|
|
status = aribcaption_trans_bitmap_subtitle(ctx);
|
|
break;
|
|
|
|
case SUBTITLE_NONE:
|
|
default:
|
|
status = 0;
|
|
}
|
|
|
|
if (status < 0) {
|
|
av_log(ctx, AV_LOG_ERROR, "Failed to set Subtitle: %s\n",
|
|
av_err2str(status));
|
|
aribcc_caption_cleanup(&ctx->caption);
|
|
return status;
|
|
}
|
|
if (status > 0) {
|
|
*got_sub_ptr = 1;
|
|
if (ctx->avpkt->pts != AV_NOPTS_VALUE)
|
|
sub->pts = av_rescale_q(ctx->avpkt->pts,
|
|
ctx->time_base, AV_TIME_BASE_Q);
|
|
if (ctx->caption.wait_duration == ARIBCC_DURATION_INDEFINITE)
|
|
sub->end_display_time = UINT32_MAX;
|
|
else
|
|
sub->end_display_time = (uint32_t)ctx->caption.wait_duration;
|
|
}
|
|
|
|
aribcc_caption_cleanup(&ctx->caption);
|
|
return avpkt->size;
|
|
}
|
|
|
|
static void aribcaption_flush(AVCodecContext *avctx)
|
|
{
|
|
ARIBCaptionContext *ctx = avctx->priv_data;
|
|
|
|
if (ctx->decoder)
|
|
aribcc_decoder_flush(ctx->decoder);
|
|
if (ctx->renderer)
|
|
aribcc_renderer_flush(ctx->renderer);
|
|
if (!(avctx->flags2 & AV_CODEC_FLAG2_RO_FLUSH_NOOP))
|
|
ctx->readorder = 0;
|
|
}
|
|
|
|
static av_cold int aribcaption_close(AVCodecContext *avctx)
|
|
{
|
|
ARIBCaptionContext *ctx = avctx->priv_data;
|
|
|
|
av_freep(&ctx->clut);
|
|
if (ctx->renderer)
|
|
aribcc_renderer_free(ctx->renderer);
|
|
if (ctx->decoder)
|
|
aribcc_decoder_free(ctx->decoder);
|
|
if (ctx->context)
|
|
aribcc_context_free(ctx->context);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static av_cold int aribcaption_init(AVCodecContext *avctx)
|
|
{
|
|
ARIBCaptionContext *ctx = avctx->priv_data;
|
|
aribcc_profile_t profile;
|
|
int ret = 0;
|
|
|
|
ctx->avctx = avctx;
|
|
|
|
switch (avctx->profile) {
|
|
case AV_PROFILE_ARIB_PROFILE_A:
|
|
profile = ARIBCC_PROFILE_A;
|
|
/* assume 960x540 at initial state */
|
|
ctx->plane_width = 960;
|
|
ctx->plane_height = 540;
|
|
ctx->font_size = 36;
|
|
break;
|
|
case AV_PROFILE_ARIB_PROFILE_C:
|
|
profile = ARIBCC_PROFILE_C;
|
|
ctx->plane_width = 320;
|
|
ctx->plane_height = 180;
|
|
ctx->font_size = 16;
|
|
break;
|
|
default:
|
|
av_log(avctx, AV_LOG_ERROR, "Unknown or unsupported profile set.\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
/* determine BorderStyle of ASS header */
|
|
if (ctx->ignore_background)
|
|
ctx->border_style = 1;
|
|
else
|
|
ctx->border_style = 4;
|
|
ctx->charstyle = ARIBCC_CHARSTYLE_DEFAULT;
|
|
if (ctx->force_stroke_text || ctx->ignore_background)
|
|
ctx->charstyle |= ARIBCC_CHARSTYLE_STROKE;
|
|
|
|
if (!(ctx->context = aribcc_context_alloc())) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to alloc libaribcaption context.\n");
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
aribcc_context_set_logcat_callback(ctx->context, logcat_callback, avctx);
|
|
if (!(ctx->decoder = aribcc_decoder_alloc(ctx->context))) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to alloc libaribcaption decoder.\n");
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
if (!aribcc_decoder_initialize(ctx->decoder,
|
|
(enum aribcc_encoding_scheme_t) ctx->encoding_scheme,
|
|
ARIBCC_CAPTIONTYPE_CAPTION,
|
|
profile,
|
|
ARIBCC_LANGUAGEID_FIRST)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to initialize libaribcaption decoder.\n");
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
aribcc_decoder_set_replace_msz_fullwidth_ascii(ctx->decoder,
|
|
ctx->replace_msz_ascii);
|
|
aribcc_decoder_set_replace_msz_fullwidth_japanese(ctx->decoder,
|
|
ctx->replace_msz_japanese);
|
|
|
|
/* Similar behavior as ffmpeg tool to set canvas size */
|
|
if (ctx->canvas_width > 0 && ctx->canvas_height > 0 &&
|
|
(ctx->avctx->width == 0 || ctx->avctx->height == 0)) {
|
|
ctx->avctx->width = ctx->canvas_width;
|
|
ctx->avctx->height = ctx->canvas_height;
|
|
}
|
|
|
|
switch ((enum AVSubtitleType) ctx->subtitle_type) {
|
|
case SUBTITLE_ASS:
|
|
ret = set_ass_header(ctx);
|
|
if (ret != 0) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to set ASS header: %s\n",
|
|
av_err2str(ret));
|
|
return ret;
|
|
}
|
|
break;
|
|
|
|
case SUBTITLE_BITMAP:
|
|
if(!(ctx->renderer = aribcc_renderer_alloc(ctx->context))) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to alloc libaribcaption renderer.\n");
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
if(!aribcc_renderer_initialize(ctx->renderer,
|
|
ARIBCC_CAPTIONTYPE_CAPTION,
|
|
ARIBCC_FONTPROVIDER_TYPE_AUTO,
|
|
ARIBCC_TEXTRENDERER_TYPE_AUTO)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to initialize libaribcaption renderer.\n");
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
estimate_video_frame_size(ctx);
|
|
ff_dlog(ctx, "canvas: %dx%d plane: %dx%d bitmap: %dx%d frame: %dx%d\n",
|
|
ctx->avctx->width, ctx->avctx->height,
|
|
ctx->plane_width, ctx->plane_height,
|
|
ctx->bitmap_plane_width, ctx->bitmap_plane_height,
|
|
ctx->frame_width, ctx->frame_height);
|
|
if (!aribcc_renderer_set_frame_size(ctx->renderer,
|
|
ctx->frame_width, ctx->frame_height)) {
|
|
av_log(ctx, AV_LOG_ERROR,
|
|
"aribcc_renderer_set_frame_size() returned with error.\n");
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
|
|
if (!(ctx->clut = av_mallocz(AVPALETTE_SIZE)))
|
|
return AVERROR(ENOMEM);
|
|
|
|
aribcc_renderer_set_storage_policy(ctx->renderer, ARIBCC_CAPTION_STORAGE_POLICY_MINIMUM, 0);
|
|
aribcc_renderer_set_replace_drcs(ctx->renderer, ctx->replace_drcs);
|
|
aribcc_renderer_set_force_stroke_text(ctx->renderer, ctx->force_stroke_text);
|
|
aribcc_renderer_set_force_no_background(ctx->renderer, ctx->ignore_background);
|
|
aribcc_renderer_set_force_no_ruby(ctx->renderer, ctx->ignore_ruby);
|
|
aribcc_renderer_set_stroke_width(ctx->renderer, ctx->stroke_width);
|
|
aribcc_renderer_set_replace_msz_halfwidth_glyph(ctx->renderer,
|
|
ctx->replace_msz_glyph);
|
|
if (ctx->font) {
|
|
int is_nomem = 0;
|
|
size_t count = 0;
|
|
const char **font_families = NULL;
|
|
const char *fonts = ctx->font;
|
|
|
|
while (*fonts) {
|
|
const char **ff = av_realloc_array(font_families, count + 1, sizeof(*font_families));
|
|
if (!ff) {
|
|
is_nomem = 1;
|
|
break;
|
|
} else {
|
|
font_families = ff;
|
|
ff[count++] = av_get_token(&fonts, ",");
|
|
if (!ff[count - 1]) {
|
|
is_nomem = 1;
|
|
break;
|
|
} else if (*fonts)
|
|
fonts++;
|
|
}
|
|
}
|
|
if (!is_nomem && count)
|
|
aribcc_renderer_set_default_font_family(ctx->renderer, font_families, count, true);
|
|
while (count)
|
|
av_freep(&font_families[--count]);
|
|
av_freep(&font_families);
|
|
if (is_nomem)
|
|
return AVERROR(ENOMEM);
|
|
}
|
|
break;
|
|
|
|
case SUBTITLE_TEXT:
|
|
case SUBTITLE_NONE:
|
|
default:
|
|
/* do nothing */ ;
|
|
}
|
|
|
|
ctx->readorder = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if !defined(ASS_SINGLE_RECT)
|
|
# define ASS_SINGLE_RECT 0
|
|
#endif
|
|
|
|
#define OFFSET(x) offsetof(ARIBCaptionContext, x)
|
|
#define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM
|
|
static const AVOption options[] = {
|
|
{ "sub_type", "subtitle rendering type",
|
|
OFFSET(subtitle_type), AV_OPT_TYPE_INT,
|
|
{ .i64 = SUBTITLE_ASS }, SUBTITLE_NONE, SUBTITLE_ASS, SD, .unit = "type" },
|
|
{ "none", "do nothing", 0, AV_OPT_TYPE_CONST,
|
|
{ .i64 = SUBTITLE_NONE }, .flags = SD, .unit = "type" },
|
|
{ "bitmap", "bitmap rendering", 0, AV_OPT_TYPE_CONST,
|
|
{ .i64 = SUBTITLE_BITMAP }, .flags = SD, .unit = "type" },
|
|
{ "text", "plain text", 0, AV_OPT_TYPE_CONST,
|
|
{ .i64 = SUBTITLE_TEXT }, .flags = SD, .unit = "type" },
|
|
{ "ass", "formatted text", 0, AV_OPT_TYPE_CONST,
|
|
{ .i64 = SUBTITLE_ASS }, .flags = SD, .unit = "type" },
|
|
{ "caption_encoding", "encoding scheme of subtitle text",
|
|
OFFSET(encoding_scheme), AV_OPT_TYPE_INT, { .i64 = ARIBCC_ENCODING_SCHEME_AUTO },
|
|
ARIBCC_ENCODING_SCHEME_AUTO, ARIBCC_ENCODING_SCHEME_ABNT_NBR_15606_1_LATIN, SD, .unit = "encoding" },
|
|
{ "auto", "automatically detect encoding scheme", 0, AV_OPT_TYPE_CONST,
|
|
{ .i64 = ARIBCC_ENCODING_SCHEME_AUTO }, .flags = SD, .unit = "encoding" },
|
|
{ "jis", "8bit-char JIS encoding (Japanese ISDB captions)", 0, AV_OPT_TYPE_CONST,
|
|
{ .i64 = ARIBCC_ENCODING_SCHEME_ARIB_STD_B24_JIS }, .flags = SD, .unit = "encoding" },
|
|
{ "utf8", "UTF-8 encoding (Philippines ISDB-T captions)", 0, AV_OPT_TYPE_CONST,
|
|
{ .i64 = ARIBCC_ENCODING_SCHEME_ARIB_STD_B24_UTF8 }, .flags = SD, .unit = "encoding" },
|
|
{ "latin", "latin characters (SBTVD / ISDB-Tb captions used in South America)", 0, AV_OPT_TYPE_CONST,
|
|
{ .i64 = ARIBCC_ENCODING_SCHEME_ABNT_NBR_15606_1_LATIN }, .flags = SD, .unit = "encoding" },
|
|
{ "ass_single_rect", "workaround of ASS subtitle for players which can't handle multi-rectangle [ass]",
|
|
OFFSET(ass_single_rect), AV_OPT_TYPE_BOOL, { .i64 = ASS_SINGLE_RECT }, 0, 1, SD },
|
|
{ "font", "comma-separated font family [ass, bitmap]",
|
|
OFFSET(font), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, SD },
|
|
{ "force_outline_text", "always render characters with outline [(ass), bitmap]",
|
|
OFFSET(force_stroke_text), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, SD },
|
|
{ "ignore_background", "ignore rendering caption background [(ass), bitmap]",
|
|
OFFSET(ignore_background), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, SD },
|
|
{ "ignore_ruby", "ignore ruby-like characters [ass, bitmap]",
|
|
OFFSET(ignore_ruby), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, SD },
|
|
{ "outline_width", "outline width of text [(ass), bitmap]",
|
|
OFFSET(stroke_width), AV_OPT_TYPE_FLOAT, { .dbl = 1.5 }, 0.0, 3.0, SD },
|
|
{ "replace_drcs", "replace known DRCS [bitmap]",
|
|
OFFSET(replace_drcs), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, SD },
|
|
{ "replace_msz_ascii", "replace MSZ fullwidth alphanumerics with halfwidth alphanumerics [ass, bitmap]",
|
|
OFFSET(replace_msz_ascii), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, SD },
|
|
{ "replace_msz_japanese", "replace MSZ fullwidth Japanese with halfwidth [ass, bitmap]",
|
|
OFFSET(replace_msz_japanese), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, SD },
|
|
{ "replace_msz_glyph", "replace MSZ characters with halfwidth glyphs [bitmap]",
|
|
OFFSET(replace_msz_glyph), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, SD },
|
|
{"canvas_size", "set input video size (WxH or abbreviation) [bitmap]",
|
|
OFFSET(canvas_width), AV_OPT_TYPE_IMAGE_SIZE, { .str = NULL }, 0, INT_MAX, SD },
|
|
{ NULL }
|
|
};
|
|
|
|
static const AVClass aribcaption_class = {
|
|
.class_name = "aribcaption decoder",
|
|
.item_name = av_default_item_name,
|
|
.option = options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
};
|
|
|
|
const FFCodec ff_libaribcaption_decoder = {
|
|
.p.name = "libaribcaption",
|
|
.p.long_name = NULL_IF_CONFIG_SMALL("ARIB STD-B24 caption decoder"),
|
|
.p.type = AVMEDIA_TYPE_SUBTITLE,
|
|
.p.id = AV_CODEC_ID_ARIB_CAPTION,
|
|
.priv_data_size = sizeof(ARIBCaptionContext),
|
|
.init = aribcaption_init,
|
|
.close = aribcaption_close,
|
|
FF_CODEC_DECODE_SUB_CB(aribcaption_decode),
|
|
.flush = aribcaption_flush,
|
|
.p.priv_class = &aribcaption_class,
|
|
.caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
|
|
};
|