mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2026-11-06 05:17:42 +00:00
The bitmap path allocated one rect per rendered image but filled them, freed them and placed them by looping over the caption regions. The renderer does not draw every region: with ignore_ruby it drops ruby regions, and it always drops regions that are too small to draw. A caption with one ordinary and one ruby region then wrote past the end of the rects array. Loop over the images instead. The renderer does not say which region an image comes from, so the CLUT can no longer be seeded from "its" region. Seed it from the colors of every region that may be drawn; colors that are not seeded are still added from the image itself, as before. While at it, use bounded insertion for every CLUT seed color, including the text and background colors added in the character loop. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdDiDRusmi6eD7qSrKBjHb Reported-by: Forgejo_Fairy Reference: #24992
1207 lines
43 KiB
C
1207 lines
43 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 */
|
|
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));
|
|
}
|
|
|
|
/* 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;
|
|
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:
|
|
ff_dlog(ctx, "no image\n");
|
|
/* A caption carrying an explicit clear screen (CS) produces 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 regionless caption 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;
|
|
|
|
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) {
|
|
aribcc_render_result_cleanup(&ctx->render_result);
|
|
ff_dlog(ctx, "no image (%d)\n", ctx->render_result.image_count);
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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. */
|
|
clut_reset(ctx);
|
|
for (int i = 0; i < ctx->caption.region_count; i++)
|
|
if (!(ctx->ignore_ruby && ctx->caption.regions[i].is_ruby))
|
|
clut_add_region(ctx, &ctx->caption.regions[i]);
|
|
|
|
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));
|
|
}
|
|
|
|
static int aribcaption_trans_ass_subtitle(ARIBCaptionContext *ctx)
|
|
{
|
|
AVSubtitle *sub = ctx->sub;
|
|
AVBPrint buf;
|
|
bool single_rect = ctx->ass_single_rect;
|
|
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;
|
|
|
|
if (ctx->caption.region_count == 0) {
|
|
/* Only an explicit clear screen (CS) erases the previous caption;
|
|
a statement containing only TIME also has no region. */
|
|
if (!(ctx->caption.flags & ARIBCC_CAPTIONFLAGS_CLEARSCREEN))
|
|
return 0;
|
|
/* clear previous caption for indefinite duration */
|
|
sub->format = 1; /* text */
|
|
ret = ff_ass_add_rect(sub, "", ctx->readorder++, 0, NULL, NULL);
|
|
return ret < 0 ? ret : 1;
|
|
}
|
|
|
|
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++) {
|
|
rx = ctx->caption.regions[i].x;
|
|
ry = ctx->caption.regions[i].y;
|
|
if (rx < x)
|
|
x = rx;
|
|
if (ry < y)
|
|
y = ry;
|
|
}
|
|
av_bprintf(&buf, "{\\an7}");
|
|
if (y < 0)
|
|
y += ctx->plane_height;
|
|
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->is_ruby && ctx->ignore_ruby)
|
|
continue;
|
|
|
|
if (!single_rect) {
|
|
int x = region->x;
|
|
int y = region->y;
|
|
if (x < 0)
|
|
x += ctx->plane_width;
|
|
if (y < 0)
|
|
y += ctx->plane_height;
|
|
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) {
|
|
if (i + 1 < ctx->caption.region_count)
|
|
av_bprintf(&buf, "{\\r}\\N");
|
|
ff_dlog(ctx, "ASS subtitle%s (%d,%d) %dx%d [%d]\n",
|
|
(region->is_ruby) ? " (ruby)" : "",
|
|
region->x, region->y, region->width, region->height,
|
|
rect_idx);
|
|
} 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 (!single_rect && !rect_idx) {
|
|
/* Every region was skipped as ruby. A clear screen (CS) must still
|
|
erase the previous caption. */
|
|
av_bprint_finalize(&buf, NULL);
|
|
if (!(ctx->caption.flags & ARIBCC_CAPTIONFLAGS_CLEARSCREEN))
|
|
return 0;
|
|
ret = ff_ass_add_rect(sub, "", ctx->readorder++, 0, NULL, NULL);
|
|
return ret < 0 ? ret : 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 < ctx->caption.region_count; 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;
|
|
const char *text;
|
|
|
|
/* Only an explicit clear screen (CS) erases the previous caption;
|
|
a statement containing only TIME also has no region. */
|
|
if (ctx->caption.region_count == 0 &&
|
|
!(ctx->caption.flags & ARIBCC_CAPTIONFLAGS_CLEARSCREEN))
|
|
return 0;
|
|
|
|
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];
|
|
|
|
if (ctx->caption.region_count == 0)
|
|
text = ""; /* clear previous caption */
|
|
else {
|
|
text = ctx->caption.text;
|
|
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,
|
|
};
|