mirror of https://github.com/HandBrake/HandBrake
1379 lines
38 KiB
C
1379 lines
38 KiB
C
/* rendersub.c
|
|
|
|
Copyright (c) 2003-2025 HandBrake Team
|
|
This file is part of the HandBrake source code
|
|
Homepage: <http://handbrake.fr/>.
|
|
It may be used under the terms of the GNU General Public License v2.
|
|
For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
|
|
*/
|
|
|
|
#include "handbrake/handbrake.h"
|
|
#include "handbrake/hbffmpeg.h"
|
|
#include "handbrake/extradata.h"
|
|
#include <ass/ass.h>
|
|
|
|
#define ABS(a) ((a) > 0 ? (a) : (-(a)))
|
|
|
|
typedef struct hb_box_s
|
|
{
|
|
int x1, y1, x2, y2;
|
|
} hb_box_t;
|
|
|
|
typedef struct hb_box_vec_s
|
|
{
|
|
hb_box_t *boxes;
|
|
int count;
|
|
int size;
|
|
} hb_box_vec_t;
|
|
|
|
struct hb_filter_private_s
|
|
{
|
|
// Common
|
|
int pix_fmt_alpha;
|
|
int hshift;
|
|
int wshift;
|
|
int crop[4];
|
|
int type;
|
|
struct SwsContext *sws;
|
|
int sws_width;
|
|
int sws_height;
|
|
|
|
hb_buffer_list_t rendered_sub_list;
|
|
int changed;
|
|
|
|
// VOBSUB && PGSSUB
|
|
hb_buffer_list_t sub_list; // List of active subs
|
|
|
|
// SSA
|
|
ASS_Library *ssa;
|
|
ASS_Renderer *renderer;
|
|
ASS_Track *ssa_track;
|
|
uint8_t script_initialized;
|
|
hb_box_vec_t boxes;
|
|
hb_csp_convert_f rgb2yuv_fn;
|
|
|
|
// SRT
|
|
int line;
|
|
hb_buffer_t *current_sub;
|
|
|
|
hb_blend_object_t *blend;
|
|
unsigned chroma_coeffs[2][4];
|
|
|
|
hb_filter_init_t input;
|
|
hb_filter_init_t output;
|
|
};
|
|
|
|
// VOBSUB
|
|
static int vobsub_post_init(hb_filter_object_t *filter, hb_job_t *job);
|
|
|
|
static int vobsub_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out);
|
|
|
|
static void vobsub_close(hb_filter_object_t *filter);
|
|
|
|
|
|
// SSA
|
|
static int ssa_post_init(hb_filter_object_t *filter, hb_job_t *job);
|
|
|
|
static int ssa_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out);
|
|
|
|
static void ssa_close(hb_filter_object_t *filter);
|
|
|
|
|
|
// SRT
|
|
static int textsub_post_init(hb_filter_object_t *filter, hb_job_t *job);
|
|
|
|
static int cc608sub_post_init(hb_filter_object_t *filter, hb_job_t *job);
|
|
|
|
static int textsub_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out);
|
|
|
|
static void textsub_close(hb_filter_object_t *filter);
|
|
|
|
|
|
// PGS
|
|
static int pgssub_post_init(hb_filter_object_t *filter, hb_job_t *job);
|
|
|
|
static int pgssub_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out);
|
|
|
|
static void pgssub_close(hb_filter_object_t *filter);
|
|
|
|
|
|
// Entry points
|
|
static int hb_rendersub_init(hb_filter_object_t *filter,
|
|
hb_filter_init_t *init);
|
|
|
|
static int hb_rendersub_post_init(hb_filter_object_t *filter, hb_job_t *job);
|
|
|
|
static int hb_rendersub_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out);
|
|
|
|
static void hb_rendersub_close(hb_filter_object_t *filter);
|
|
|
|
hb_filter_object_t hb_filter_render_sub =
|
|
{
|
|
.id = HB_FILTER_RENDER_SUB,
|
|
.enforce_order = 1,
|
|
.name = "Subtitle renderer",
|
|
.settings = NULL,
|
|
.init = hb_rendersub_init,
|
|
.post_init = hb_rendersub_post_init,
|
|
.work = hb_rendersub_work,
|
|
.close = hb_rendersub_close,
|
|
};
|
|
|
|
static void hb_box_vec_resize(hb_box_vec_t *vec, int size)
|
|
{
|
|
hb_box_t *boxes = realloc(vec->boxes, size * sizeof(hb_box_t));
|
|
if (boxes == NULL)
|
|
{
|
|
return; // Error. Should never happen.
|
|
}
|
|
vec->boxes = boxes;
|
|
vec->size = size;
|
|
}
|
|
|
|
static inline int hb_box_intersect(const hb_box_t *a, const hb_box_t *b, int offset)
|
|
{
|
|
return ((FFMIN(a->x2, b->x2) + offset - FFMAX(a->x1, b->x1)) >= 0) &&
|
|
((FFMIN(a->y2, b->y2) + offset - FFMAX(a->y1, b->y1)) >= 0);
|
|
}
|
|
|
|
static inline void hb_box_union(hb_box_t *a, const hb_box_t *b)
|
|
{
|
|
if (a->x1 > b->x1) { a->x1 = b->x1; }
|
|
if (a->y1 > b->y1) { a->y1 = b->y1; }
|
|
if (a->x2 < b->x2) { a->x2 = b->x2; }
|
|
if (a->y2 < b->y2) { a->y2 = b->y2; }
|
|
}
|
|
|
|
static inline void hb_box_clear(hb_box_t *box)
|
|
{
|
|
box->x1 = box->y1 = box->x2 = box->y2 = 0;
|
|
}
|
|
|
|
static void hb_box_vec_merge(hb_box_vec_t *vec)
|
|
{
|
|
if (vec->count < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < vec->count - 1; i++)
|
|
{
|
|
hb_box_t *a = &vec->boxes[i];
|
|
for (int j = i + 1; j < vec->count; j++)
|
|
{
|
|
hb_box_t *b = &vec->boxes[j];
|
|
if (hb_box_intersect(a, b, 8))
|
|
{
|
|
hb_box_union(a, b);
|
|
hb_box_clear(b);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void hb_box_vec_compact(hb_box_vec_t *vec)
|
|
{
|
|
int j = 0, i = 0;
|
|
while (i < vec->count)
|
|
{
|
|
hb_box_t a = vec->boxes[i];
|
|
if (a.x2 != 0 || a.y2 != 0)
|
|
{
|
|
vec->boxes[j] = a;
|
|
j++;
|
|
}
|
|
i++;
|
|
}
|
|
vec->count = j;
|
|
}
|
|
|
|
static void hb_box_vec_append(hb_box_vec_t *vec, int x1, int y1, int x2, int y2)
|
|
{
|
|
if (x1 == x2 || y1 == y2)
|
|
{
|
|
return; // Empty box
|
|
}
|
|
|
|
if (vec->size < vec->count + 1)
|
|
{
|
|
hb_box_vec_resize(vec, vec->count + 10);
|
|
}
|
|
|
|
if (vec->size < vec->count + 1)
|
|
{
|
|
return; // Error. Should never happen.
|
|
}
|
|
|
|
vec->boxes[vec->count].x1 = x1;
|
|
vec->boxes[vec->count].y1 = y1;
|
|
vec->boxes[vec->count].x2 = x2;
|
|
vec->boxes[vec->count].y2 = y2;
|
|
vec->count += 1;
|
|
|
|
hb_box_vec_merge(vec);
|
|
hb_box_vec_compact(vec);
|
|
}
|
|
|
|
static void hb_box_vec_clear(hb_box_vec_t *vec)
|
|
{
|
|
memset(vec->boxes, 0, sizeof(hb_box_t) * vec->size);
|
|
vec->count = 0;
|
|
}
|
|
|
|
static void hb_box_vec_close(hb_box_vec_t *vec)
|
|
{
|
|
free(vec->boxes);
|
|
vec->boxes = NULL;
|
|
vec->count = 0;
|
|
vec->size = 0;
|
|
}
|
|
|
|
static hb_buffer_t * scale_subtitle(hb_filter_private_t *pv,
|
|
hb_buffer_t *sub, hb_buffer_t *buf)
|
|
{
|
|
hb_buffer_t *scaled;
|
|
double xfactor = 1., yfactor = 1.;
|
|
|
|
// Do we need to rescale subtitles?
|
|
if (sub->f.window_width > 0 && sub->f.window_height > 0)
|
|
{
|
|
// TODO: Factor aspect ratio
|
|
// For now, assume subtitle and video PAR is the same.
|
|
xfactor = (double)buf->f.width / sub->f.window_width;
|
|
yfactor = (double)buf->f.height / sub->f.window_height;
|
|
// The video may have been cropped. This will make xfactor != yfactor
|
|
// even though video and subtitles are the same PAR. So use the
|
|
// larger of as the scale factor.
|
|
if (xfactor > yfactor)
|
|
{
|
|
yfactor = xfactor;
|
|
}
|
|
else
|
|
{
|
|
xfactor = yfactor;
|
|
}
|
|
}
|
|
if (ABS(xfactor - 1) > 0.01 || ABS(yfactor - 1) > 0.01)
|
|
{
|
|
uint8_t *in_data[4], *out_data[4];
|
|
int in_stride[4], out_stride[4];
|
|
int width, height;
|
|
|
|
width = sub->f.width * xfactor;
|
|
height = sub->f.height * yfactor;
|
|
// Note that subtitle frame buffer has alpha and should always be 444P
|
|
scaled = hb_frame_buffer_init(sub->f.fmt, width, height);
|
|
if (scaled == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
scaled->f.x = sub->f.x * xfactor;
|
|
scaled->f.y = sub->f.y * yfactor;
|
|
|
|
hb_picture_fill(in_data, in_stride, sub);
|
|
hb_picture_fill(out_data, out_stride, scaled);
|
|
|
|
if (pv->sws == NULL ||
|
|
pv->sws_width != width ||
|
|
pv->sws_height != height)
|
|
{
|
|
if (pv->sws!= NULL)
|
|
{
|
|
sws_freeContext(pv->sws);
|
|
}
|
|
pv->sws = hb_sws_get_context(
|
|
sub->f.width, sub->f.height, sub->f.fmt, AVCOL_RANGE_MPEG,
|
|
scaled->f.width, scaled->f.height, sub->f.fmt, AVCOL_RANGE_MPEG,
|
|
SWS_LANCZOS|SWS_ACCURATE_RND, SWS_CS_DEFAULT);
|
|
pv->sws_width = width;
|
|
pv->sws_height = height;
|
|
}
|
|
sws_scale(pv->sws, (const uint8_t* const *)in_data, in_stride,
|
|
0, sub->f.height, out_data, out_stride);
|
|
}
|
|
else
|
|
{
|
|
scaled = hb_buffer_dup(sub);
|
|
}
|
|
|
|
int top, left, margin_top, margin_percent;
|
|
|
|
// Percent of height of picture that form a margin that subtitles
|
|
//should not be displayed within.
|
|
margin_percent = 2;
|
|
|
|
// If necessary, move the subtitle so it is not in a cropped zone.
|
|
// When it won't fit, we center it so we lose as much on both ends.
|
|
// Otherwise we try to leave a 20px or 2% margin around it.
|
|
margin_top = ((buf->f.height - pv->crop[0] - pv->crop[1]) *
|
|
margin_percent) / 100;
|
|
|
|
if (margin_top > 20)
|
|
{
|
|
// A maximum margin of 20px regardless of height of the picture.
|
|
margin_top = 20;
|
|
}
|
|
|
|
if (scaled->f.height > buf->f.height - pv->crop[0] - pv->crop[1] - (margin_top * 2))
|
|
{
|
|
// The subtitle won't fit in the cropped zone, so center
|
|
// it vertically so we fit in as much as we can.
|
|
top = pv->crop[0] + (buf->f.height - pv->crop[0] -
|
|
pv->crop[1] - scaled->f.height) / 2;
|
|
}
|
|
else if (scaled->f.y < pv->crop[0] + margin_top)
|
|
{
|
|
// The subtitle fits in the cropped zone, but is currently positioned
|
|
// within our top margin, so move it outside of our margin.
|
|
top = pv->crop[0] + margin_top;
|
|
}
|
|
else if (scaled->f.y > buf->f.height - pv->crop[1] - margin_top - scaled->f.height)
|
|
{
|
|
// The subtitle fits in the cropped zone, and is not within the top
|
|
// margin but is within the bottom margin, so move it to be above
|
|
// the margin.
|
|
top = buf->f.height - pv->crop[1] - margin_top - scaled->f.height;
|
|
}
|
|
else
|
|
{
|
|
// The subtitle is fine where it is.
|
|
top = scaled->f.y;
|
|
}
|
|
|
|
if (scaled->f.width > buf->f.width - pv->crop[2] - pv->crop[3] - 40)
|
|
{
|
|
left = pv->crop[2] + (buf->f.width - pv->crop[2] -
|
|
pv->crop[3] - scaled->f.width) / 2;
|
|
}
|
|
else if (scaled->f.x < pv->crop[2] + 20)
|
|
{
|
|
left = pv->crop[2] + 20;
|
|
}
|
|
else if (scaled->f.x > buf->f.width - pv->crop[3] - 20 - scaled->f.width)
|
|
{
|
|
left = buf->f.width - pv->crop[3] - 20 - scaled->f.width;
|
|
}
|
|
else
|
|
{
|
|
left = scaled->f.x;
|
|
}
|
|
|
|
scaled->f.x = left;
|
|
scaled->f.y = top;
|
|
|
|
return scaled;
|
|
}
|
|
|
|
// Assumes that the input buffer has the same dimensions
|
|
// as the original title dimensions
|
|
static void render_vobsubs(hb_filter_private_t *pv, hb_buffer_t *buf)
|
|
{
|
|
hb_buffer_t *sub, *next;
|
|
|
|
// Note that VOBSUBs can overlap in time.
|
|
// I.e. more than one may be rendered to the screen at once.
|
|
for (sub = hb_buffer_list_head(&pv->sub_list); sub; sub = next)
|
|
{
|
|
next = sub->next;
|
|
|
|
if ((sub->s.stop != AV_NOPTS_VALUE && sub->s.stop <= buf->s.start) ||
|
|
(next != NULL && sub->s.stop == AV_NOPTS_VALUE && next->s.start <= buf->s.start))
|
|
{
|
|
// Subtitle stop is in the past, delete it
|
|
hb_buffer_list_rem(&pv->sub_list, sub);
|
|
hb_buffer_close(&sub);
|
|
}
|
|
else if (sub->s.start <= buf->s.start)
|
|
{
|
|
// The subtitle has started before this frame and ends
|
|
// after it. Render the subtitle into the frame.
|
|
hb_buffer_t *scaled = scale_subtitle(pv, sub, buf);
|
|
if (scaled)
|
|
{
|
|
hb_buffer_list_append(&pv->rendered_sub_list, scaled);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The subtitle starts in the future. No need to continue.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int vobsub_post_init(hb_filter_object_t *filter, hb_job_t *job)
|
|
{
|
|
filter->private_data->pix_fmt_alpha = AV_PIX_FMT_YUVA444P;
|
|
return 0;
|
|
}
|
|
|
|
static void vobsub_close(hb_filter_object_t *filter)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
|
|
if (!pv)
|
|
{
|
|
return;
|
|
}
|
|
|
|
hb_buffer_list_close(&pv->sub_list);
|
|
|
|
free(pv);
|
|
filter->private_data = NULL;
|
|
}
|
|
|
|
static int vobsub_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
hb_buffer_t *in = *buf_in;
|
|
hb_buffer_t *sub;
|
|
|
|
if (in->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
*buf_in = NULL;
|
|
*buf_out = in;
|
|
return HB_FILTER_DONE;
|
|
}
|
|
|
|
// Get any pending subtitles and add them to the active
|
|
// subtitle list
|
|
while ((sub = hb_fifo_get(filter->subtitle->fifo_out)))
|
|
{
|
|
if (sub->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
hb_buffer_close(&sub);
|
|
break;
|
|
}
|
|
hb_buffer_list_append(&pv->sub_list, sub);
|
|
}
|
|
|
|
render_vobsubs(pv, in);
|
|
|
|
*buf_in = NULL;
|
|
*buf_out = pv->blend->work(pv->blend, in, &pv->rendered_sub_list, 1);
|
|
|
|
hb_buffer_list_close(&pv->rendered_sub_list);
|
|
|
|
return HB_FILTER_OK;
|
|
}
|
|
|
|
#define ALPHA_BLEND(srcA, srcRGB, dstAc, dstRGB, outA) \
|
|
(((srcA * srcRGB + dstRGB * dstAc + (outA >> 1)) / outA))
|
|
#define div255(x) (((x + ((x + 128) >> 8)) + 128) >> 8)
|
|
|
|
static uint8_t inline ssa_alpha(const ASS_Image *frame, const int x, const int y)
|
|
{
|
|
const unsigned frame_a = (frame->color) & 0xff;
|
|
const unsigned gliph_a = frame->bitmap[y*frame->stride + x];
|
|
|
|
// Alpha for this pixel is the frame opacity (255 - frame_a)
|
|
// multiplied by the gliph alpha (gliph_a) for this pixel
|
|
return (uint8_t)div255((255 - frame_a) * gliph_a);
|
|
}
|
|
|
|
static hb_buffer_t * compose_subsample_ass(hb_filter_private_t *pv, const ASS_Image *frame_list,
|
|
const unsigned width, const unsigned height,
|
|
const unsigned x, const unsigned y)
|
|
{
|
|
const ASS_Image *frame = frame_list;
|
|
const unsigned flat_stride = width << 2;
|
|
uint8_t *compo = calloc(flat_stride * height, sizeof(uint8_t));
|
|
|
|
if (!compo)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
while (frame)
|
|
{
|
|
if (frame->w && frame->h &&
|
|
x <= frame->dst_x && x + width >= frame->dst_x + frame->w &&
|
|
y <= frame->dst_y && y + height >= frame->dst_y + frame->h)
|
|
{
|
|
const int yuv = pv->rgb2yuv_fn(frame->color >> 8);
|
|
|
|
const unsigned frame_y = (yuv >> 16) & 0xff;
|
|
const unsigned frame_v = (yuv >> 8 ) & 0xff;
|
|
const unsigned frame_u = (yuv >> 0 ) & 0xff;
|
|
unsigned frame_a, res_alpha, alpha_compo, alpha_in_scaled;
|
|
|
|
const unsigned ini_fx = (frame->dst_x - x) * 4 + (frame->dst_y - y) * flat_stride;
|
|
|
|
for (int yy = 0, fx = ini_fx; yy < frame->h; yy++, fx = ini_fx + yy * flat_stride)
|
|
{
|
|
for (int xx = 0; xx < frame->w; xx++, fx += 4)
|
|
{
|
|
frame_a = ssa_alpha(frame, xx, yy);
|
|
|
|
// Skip if transparent
|
|
if (frame_a)
|
|
{
|
|
if (compo[fx+3])
|
|
{
|
|
alpha_in_scaled = frame_a * 255;
|
|
alpha_compo = compo[fx+3] * (255 - frame_a);
|
|
res_alpha = (alpha_in_scaled + alpha_compo);
|
|
|
|
compo[fx ] = ALPHA_BLEND(alpha_in_scaled, frame_y, alpha_compo, compo[fx ], res_alpha);
|
|
compo[fx+1] = ALPHA_BLEND(alpha_in_scaled, frame_u, alpha_compo, compo[fx+1], res_alpha);
|
|
compo[fx+2] = ALPHA_BLEND(alpha_in_scaled, frame_v, alpha_compo, compo[fx+2], res_alpha);
|
|
compo[fx+3] = div255(res_alpha);
|
|
}
|
|
else
|
|
{
|
|
compo[fx ] = frame_y;
|
|
compo[fx+1] = frame_u;
|
|
compo[fx+2] = frame_v;
|
|
compo[fx+3] = frame_a;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
frame = frame->next;
|
|
}
|
|
|
|
hb_buffer_t *sub = hb_frame_buffer_init(pv->pix_fmt_alpha, width, height);
|
|
if (!sub)
|
|
{
|
|
free(compo);
|
|
return NULL;
|
|
}
|
|
|
|
sub->f.x = x;
|
|
sub->f.y = y;
|
|
|
|
uint8_t *y_out, *u_out, *v_out, *a_out;
|
|
y_out = sub->plane[0].data;
|
|
u_out = sub->plane[1].data;
|
|
v_out = sub->plane[2].data;
|
|
a_out = sub->plane[3].data;
|
|
|
|
unsigned int accu_a, accu_b, accu_c, coeff, is_chroma_line;
|
|
|
|
for (int yy = 0, ys = 0, fx = 0; yy < height; yy++, ys = yy >> pv->hshift, fx = yy * flat_stride)
|
|
{
|
|
is_chroma_line = yy == ys << pv->hshift;
|
|
for (int xx = 0, xs = 0; xx < width; xx++, xs = xx >> pv->wshift, fx += 4)
|
|
{
|
|
y_out[xx] = compo[ fx ];
|
|
a_out[xx] = compo[fx+3];
|
|
|
|
// Are we on a chroma sample?
|
|
if (is_chroma_line && xx == xs << pv->wshift)
|
|
{
|
|
accu_a = accu_b = accu_c = 0;
|
|
for (int yz = 0; yz < (1 << pv->hshift) && (yz + yy < height); yz++)
|
|
{
|
|
for (int xz = 0; xz < (1 << pv->wshift) && (xz + xx < width); xz++)
|
|
{
|
|
// Access compo to avoid cache misses with a_out.
|
|
coeff = pv->chroma_coeffs[0][xz] *
|
|
pv->chroma_coeffs[1][yz] *
|
|
compo[fx + yz * flat_stride + 3];
|
|
accu_a += coeff * compo[fx + yz * flat_stride + 1];
|
|
accu_b += coeff * compo[fx + yz * flat_stride + 2];
|
|
accu_c += coeff;
|
|
}
|
|
}
|
|
if (accu_c)
|
|
{
|
|
u_out[xs] = (accu_a + (accu_c >> 1)) / accu_c;
|
|
v_out[xs] = (accu_b + (accu_c >> 1)) / accu_c;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_chroma_line)
|
|
{
|
|
u_out += sub->plane[1].stride;
|
|
v_out += sub->plane[2].stride;
|
|
}
|
|
y_out += sub->plane[0].stride;
|
|
a_out += sub->plane[3].stride;
|
|
}
|
|
|
|
free(compo);
|
|
return sub;
|
|
}
|
|
|
|
static void clear_ssa_rendered_sub_cache(hb_filter_private_t *pv)
|
|
{
|
|
if (hb_buffer_list_count(&pv->rendered_sub_list))
|
|
{
|
|
hb_buffer_list_close(&pv->rendered_sub_list);
|
|
hb_box_vec_clear(&pv->boxes);
|
|
}
|
|
}
|
|
|
|
static void render_ssa_subs(hb_filter_private_t *pv, int64_t start)
|
|
{
|
|
int changed;
|
|
ASS_Image *frame_list = ass_render_frame(pv->renderer, pv->ssa_track,
|
|
start / 90, &changed);
|
|
if (!frame_list)
|
|
{
|
|
clear_ssa_rendered_sub_cache(pv);
|
|
}
|
|
else if (changed)
|
|
{
|
|
// Re-use cached overlays, whenever possible
|
|
clear_ssa_rendered_sub_cache(pv);
|
|
|
|
// Find overlay size and pos of non overlapped boxes
|
|
// (faster than composing at the video dimensions)
|
|
for (ASS_Image *frame = frame_list; frame; frame = frame->next)
|
|
{
|
|
hb_box_vec_append(&pv->boxes,
|
|
frame->dst_x, frame->dst_y,
|
|
frame->w + frame->dst_x, frame->h + frame->dst_y);
|
|
}
|
|
|
|
for (int i = 0; i < pv->boxes.count; i++)
|
|
{
|
|
// Overlay must be aligned to the chroma plane, pad as needed.
|
|
hb_box_t box = pv->boxes.boxes[i];
|
|
int x = box.x1 - ((box.x1 + pv->crop[2]) & ((1 << pv->wshift) - 1));
|
|
int y = box.y1 - ((box.y1 + pv->crop[0]) & ((1 << pv->hshift) - 1));
|
|
int width = box.x2 - x;
|
|
int height = box.y2 - y;
|
|
|
|
hb_buffer_t *sub = compose_subsample_ass(pv, frame_list, width, height, x, y);
|
|
if (sub)
|
|
{
|
|
sub->f.x += pv->crop[2];
|
|
sub->f.y += pv->crop[0];
|
|
hb_buffer_list_append(&pv->rendered_sub_list, sub);
|
|
}
|
|
}
|
|
}
|
|
pv->changed = changed;
|
|
}
|
|
|
|
static void ssa_log(int level, const char *fmt, va_list args, void *data)
|
|
{
|
|
if (level < 5) // Same as default verbosity when no callback is set
|
|
{
|
|
hb_valog(1, "[ass]", fmt, args);
|
|
}
|
|
}
|
|
|
|
static int ssa_post_init(hb_filter_object_t *filter, hb_job_t *job)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
|
|
switch (pv->input.pix_fmt)
|
|
{
|
|
case AV_PIX_FMT_NV12:
|
|
case AV_PIX_FMT_P010:
|
|
case AV_PIX_FMT_P012:
|
|
case AV_PIX_FMT_P016:
|
|
case AV_PIX_FMT_YUV420P:
|
|
case AV_PIX_FMT_YUV420P10:
|
|
case AV_PIX_FMT_YUV420P12:
|
|
case AV_PIX_FMT_YUV420P16:
|
|
pv->pix_fmt_alpha = AV_PIX_FMT_YUVA420P;
|
|
break;
|
|
case AV_PIX_FMT_NV16:
|
|
case AV_PIX_FMT_P210:
|
|
case AV_PIX_FMT_P212:
|
|
case AV_PIX_FMT_P216:
|
|
case AV_PIX_FMT_YUV422P:
|
|
case AV_PIX_FMT_YUV422P10:
|
|
case AV_PIX_FMT_YUV422P12:
|
|
case AV_PIX_FMT_YUV422P16:
|
|
pv->pix_fmt_alpha = AV_PIX_FMT_YUVA422P;
|
|
break;
|
|
case AV_PIX_FMT_NV24:
|
|
case AV_PIX_FMT_P410:
|
|
case AV_PIX_FMT_P412:
|
|
case AV_PIX_FMT_P416:
|
|
case AV_PIX_FMT_YUV444P:
|
|
case AV_PIX_FMT_YUV444P10:
|
|
case AV_PIX_FMT_YUV444P12:
|
|
case AV_PIX_FMT_YUV444P16:
|
|
default:
|
|
pv->pix_fmt_alpha = AV_PIX_FMT_YUVA444P;
|
|
break;
|
|
}
|
|
|
|
pv->ssa = ass_library_init();
|
|
if (!pv->ssa)
|
|
{
|
|
hb_error("decssasub: libass initialization failed\n");
|
|
return 1;
|
|
}
|
|
|
|
// Redirect libass output to hb_log
|
|
ass_set_message_cb(pv->ssa, ssa_log, NULL);
|
|
|
|
// Load embedded fonts
|
|
hb_list_t *list_attachment = job->list_attachment;
|
|
for (int i = 0; i < hb_list_count(list_attachment); i++)
|
|
{
|
|
hb_attachment_t *attachment = hb_list_item(list_attachment, i);
|
|
|
|
if (attachment->type == FONT_TTF_ATTACH ||
|
|
attachment->type == FONT_OTF_ATTACH)
|
|
{
|
|
ass_add_font(pv->ssa,
|
|
attachment->name,
|
|
attachment->data,
|
|
attachment->size);
|
|
}
|
|
}
|
|
|
|
ass_set_extract_fonts(pv->ssa, 1);
|
|
ass_set_style_overrides(pv->ssa, NULL);
|
|
|
|
pv->renderer = ass_renderer_init(pv->ssa);
|
|
if (!pv->renderer)
|
|
{
|
|
hb_log("decssasub: renderer initialization failed\n");
|
|
return 1;
|
|
}
|
|
|
|
ass_set_use_margins(pv->renderer, 0);
|
|
ass_set_hinting(pv->renderer, ASS_HINTING_NONE);
|
|
ass_set_font_scale(pv->renderer, 1.0);
|
|
ass_set_line_spacing(pv->renderer, 1.0);
|
|
|
|
// Setup default font family
|
|
//
|
|
// SSA v4.00 requires that "Arial" be the default font
|
|
const char *font = NULL;
|
|
const char *family = "Arial";
|
|
// NOTE: This can sometimes block for several *seconds*.
|
|
// It seems that process_fontdata() for some embedded fonts is slow.
|
|
ass_set_fonts(pv->renderer, font, family, /*haveFontConfig=*/1, NULL, 1);
|
|
|
|
// Setup track state
|
|
pv->ssa_track = ass_new_track(pv->ssa);
|
|
if (!pv->ssa_track)
|
|
{
|
|
hb_log("decssasub: ssa track initialization failed\n");
|
|
return 1;
|
|
}
|
|
|
|
// Do not use Read Order to eliminate duplicates
|
|
// we never send the same subtitles sample twice,
|
|
// and some MKVs have duplicated Read Orders
|
|
// and won't render properly when this is enabled.
|
|
ass_set_check_readorder(pv->ssa_track, 0);
|
|
|
|
int height = job->title->geometry.height - job->crop[0] - job->crop[1];
|
|
int width = job->title->geometry.width - job->crop[2] - job->crop[3];
|
|
ass_set_frame_size(pv->renderer, width, height);
|
|
ass_set_storage_size(pv->renderer, width, height);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ssa_close(hb_filter_object_t *filter)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
|
|
if (!pv)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pv->ssa_track)
|
|
{
|
|
ass_free_track(pv->ssa_track);
|
|
}
|
|
if (pv->renderer)
|
|
{
|
|
ass_renderer_done(pv->renderer);
|
|
}
|
|
if (pv->ssa)
|
|
{
|
|
ass_library_done(pv->ssa);
|
|
}
|
|
hb_box_vec_close(&pv->boxes);
|
|
|
|
free(pv);
|
|
filter->private_data = NULL;
|
|
}
|
|
|
|
static void ssa_work_init(hb_filter_private_t *pv, const hb_data_t *sub_data)
|
|
{
|
|
// NOTE: The codec extradata is expected to be in MKV format
|
|
// I would like to initialize this in ssa_post_init, but when we are
|
|
// transcoding text subtitles to SSA, the extradata does not
|
|
// get initialized until the decoder is initialized. Since
|
|
// decoder initialization happens after filter initialization,
|
|
// we need to postpone this.
|
|
ass_process_codec_private(pv->ssa_track, (const char *)sub_data->bytes, sub_data->size);
|
|
|
|
switch(pv->ssa_track->YCbCrMatrix)
|
|
{
|
|
case YCBCR_DEFAULT: //No YCbCrMatrix header: VSFilter default
|
|
case YCBCR_FCC_TV: //FCC is almost the same as 601
|
|
case YCBCR_FCC_PC:
|
|
case YCBCR_BT601_TV:
|
|
case YCBCR_BT601_PC:
|
|
pv->rgb2yuv_fn = hb_rgb2yuv;
|
|
break;
|
|
case YCBCR_BT709_TV:
|
|
case YCBCR_BT709_PC:
|
|
case YCBCR_SMPTE240M_TV:
|
|
case YCBCR_SMPTE240M_PC: //240M is almost the same as 709
|
|
pv->rgb2yuv_fn = hb_rgb2yuv_bt709;
|
|
break;
|
|
//use video csp
|
|
case YCBCR_UNKNOWN://cannot parse
|
|
case YCBCR_NONE: //explicitely requested no override
|
|
default:
|
|
pv->rgb2yuv_fn = hb_get_rgb2yuv_function(pv->input.color_matrix);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int ssa_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
hb_buffer_t *in = *buf_in;
|
|
hb_buffer_t *sub;
|
|
|
|
if (!pv->script_initialized)
|
|
{
|
|
ssa_work_init(pv, filter->subtitle->extradata);
|
|
pv->script_initialized = 1;
|
|
}
|
|
if (in->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
*buf_in = NULL;
|
|
*buf_out = in;
|
|
return HB_FILTER_DONE;
|
|
}
|
|
|
|
// Get any pending subtitles and add them to the active
|
|
// subtitle list
|
|
while ((sub = hb_fifo_get(filter->subtitle->fifo_out)))
|
|
{
|
|
if (sub->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
hb_buffer_close(&sub);
|
|
break;
|
|
}
|
|
// Parse MKV-SSA packet
|
|
// SSA subtitles always have an explicit stop time, so we
|
|
// do not need to do special processing for stop == AV_NOPTS_VALUE
|
|
ass_process_chunk(pv->ssa_track, (char *)sub->data, sub->size,
|
|
sub->s.start / 90,
|
|
(sub->s.stop - sub->s.start) / 90);
|
|
hb_buffer_close(&sub);
|
|
}
|
|
|
|
render_ssa_subs(pv, in->s.start);
|
|
|
|
*buf_in = NULL;
|
|
*buf_out = pv->blend->work(pv->blend, in, &pv->rendered_sub_list, pv->changed);
|
|
|
|
return HB_FILTER_OK;
|
|
}
|
|
|
|
static int cc608sub_post_init(hb_filter_object_t *filter, hb_job_t *job)
|
|
{
|
|
return ssa_post_init(filter, job);
|
|
}
|
|
|
|
static int textsub_post_init(hb_filter_object_t *filter, hb_job_t *job)
|
|
{
|
|
return ssa_post_init(filter, job);
|
|
}
|
|
|
|
static void textsub_close(hb_filter_object_t *filter)
|
|
{
|
|
return ssa_close(filter);
|
|
}
|
|
|
|
static void process_sub(hb_filter_private_t *pv, hb_buffer_t *sub)
|
|
{
|
|
ass_process_chunk(pv->ssa_track, (char *)sub->data, sub->size,
|
|
sub->s.start, sub->s.stop - sub->s.start);
|
|
}
|
|
|
|
static int textsub_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
hb_buffer_t *in = *buf_in;
|
|
hb_buffer_t *sub;
|
|
|
|
if (!pv->script_initialized)
|
|
{
|
|
ssa_work_init(pv, filter->subtitle->extradata);
|
|
pv->script_initialized = 1;
|
|
}
|
|
|
|
if (in->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
*buf_in = NULL;
|
|
*buf_out = in;
|
|
return HB_FILTER_DONE;
|
|
}
|
|
|
|
int in_start_ms = in->s.start / 90;
|
|
|
|
// Get any pending subtitles and add them to the active
|
|
// subtitle list
|
|
while ((sub = hb_fifo_get(filter->subtitle->fifo_out)))
|
|
{
|
|
if (sub->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
hb_buffer_close(&sub);
|
|
if (pv->current_sub != NULL)
|
|
{
|
|
// Make us some duration for final sub
|
|
pv->current_sub->s.stop = pv->current_sub->s.start +
|
|
90000LL * 10;
|
|
process_sub(pv, pv->current_sub);
|
|
hb_buffer_close(&pv->current_sub);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// libass expects times in ms. So to make the math easy,
|
|
// convert to ms immediately.
|
|
sub->s.start /= 90;
|
|
if (sub->s.stop != AV_NOPTS_VALUE)
|
|
{
|
|
sub->s.stop /= 90;
|
|
}
|
|
|
|
// Subtitle formats such as CC can have stop times
|
|
// that are not known until an "erase display" command
|
|
// is encountered in the stream. For these formats
|
|
// current_sub is the currently active subtitle for which
|
|
// we do not yet know the stop time. We do not currently
|
|
// support overlapping subtitles of this type.
|
|
if (pv->current_sub != NULL)
|
|
{
|
|
// Next sub start time tells us the stop time of the
|
|
// current sub when it is not known in advance.
|
|
pv->current_sub->s.stop = sub->s.start;
|
|
process_sub(pv, pv->current_sub);
|
|
hb_buffer_close(&pv->current_sub);
|
|
}
|
|
if (sub->s.flags & HB_BUF_FLAG_EOS)
|
|
{
|
|
// marker used to "clear" previous sub that had
|
|
// an unknown duration
|
|
hb_buffer_close(&sub);
|
|
}
|
|
else if (sub->s.stop == AV_NOPTS_VALUE)
|
|
{
|
|
// We don't know the duration of this sub. So we will
|
|
// apply it to every video frame until we see a "clear" sub.
|
|
pv->current_sub = sub;
|
|
pv->current_sub->s.stop = pv->current_sub->s.start;
|
|
}
|
|
else
|
|
{
|
|
// Duration of this subtitle is known, so we can just
|
|
// process it normally.
|
|
process_sub(pv, sub);
|
|
hb_buffer_close(&sub);
|
|
}
|
|
}
|
|
if (pv->current_sub != NULL && pv->current_sub->s.start <= in_start_ms)
|
|
{
|
|
// We don't know the duration of this subtitle, but we know
|
|
// that it started before the current video frame and that
|
|
// it is still active. So render it on this video frame.
|
|
pv->current_sub->s.start = pv->current_sub->s.stop;
|
|
pv->current_sub->s.stop = in_start_ms + 1;
|
|
process_sub(pv, pv->current_sub);
|
|
}
|
|
|
|
render_ssa_subs(pv, in->s.start);
|
|
|
|
*buf_in = NULL;
|
|
*buf_out = pv->blend->work(pv->blend, in, &pv->rendered_sub_list, pv->changed);
|
|
|
|
return HB_FILTER_OK;
|
|
}
|
|
|
|
static void render_pgs_subs(hb_filter_private_t *pv, hb_buffer_t *buf)
|
|
{
|
|
hb_buffer_t *sub, *next, *active = NULL;
|
|
|
|
// Each PGS subtitle supersedes anything that preceded it.
|
|
// Find the active subtitle (if there is one), and delete
|
|
// everything before it.
|
|
for (sub = hb_buffer_list_head(&pv->sub_list); sub; sub = sub->next)
|
|
{
|
|
if (sub->s.start > buf->s.start)
|
|
{
|
|
break;
|
|
}
|
|
active = sub;
|
|
}
|
|
|
|
for (sub = hb_buffer_list_head(&pv->sub_list); sub; sub = next)
|
|
{
|
|
if (active == NULL || sub == active)
|
|
{
|
|
break;
|
|
}
|
|
next = sub->next;
|
|
hb_buffer_list_rem(&pv->sub_list, sub);
|
|
hb_buffer_close(&sub);
|
|
}
|
|
|
|
// Some PGS subtitles have no content and only serve to clear
|
|
// the screen. If any of these are at the front of our list,
|
|
// we can now get rid of them.
|
|
for (sub = hb_buffer_list_head(&pv->sub_list); sub; sub = next)
|
|
{
|
|
if (sub->f.width != 0 && sub->f.height != 0)
|
|
{
|
|
break;
|
|
}
|
|
next = sub->next;
|
|
hb_buffer_list_rem(&pv->sub_list, sub);
|
|
hb_buffer_close(&sub);
|
|
}
|
|
|
|
// Check to see if there's an active subtitle, and apply it.
|
|
for (sub = hb_buffer_list_head(&pv->sub_list); sub; sub = sub->next)
|
|
{
|
|
if (sub->s.start <= buf->s.start)
|
|
{
|
|
hb_buffer_t *scaled = scale_subtitle(pv, sub, buf);
|
|
if (scaled)
|
|
{
|
|
hb_buffer_list_append(&pv->rendered_sub_list, scaled);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int pgssub_post_init(hb_filter_object_t *filter, hb_job_t *job)
|
|
{
|
|
filter->private_data->pix_fmt_alpha = AV_PIX_FMT_YUVA444P;
|
|
return 0;
|
|
}
|
|
|
|
static void pgssub_close(hb_filter_object_t *filter)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
|
|
if (!pv)
|
|
{
|
|
return;
|
|
}
|
|
|
|
hb_buffer_list_close(&pv->sub_list);
|
|
|
|
free(pv);
|
|
filter->private_data = NULL;
|
|
}
|
|
|
|
static int pgssub_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
hb_buffer_t *in = *buf_in;
|
|
hb_buffer_t *sub;
|
|
|
|
if (in->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
*buf_in = NULL;
|
|
*buf_out = in;
|
|
return HB_FILTER_DONE;
|
|
}
|
|
|
|
// Get any pending subtitles and add them to the active
|
|
// subtitle list
|
|
while ((sub = hb_fifo_get(filter->subtitle->fifo_out)))
|
|
{
|
|
if (sub->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
hb_buffer_close(&sub);
|
|
break;
|
|
}
|
|
hb_buffer_list_append(&pv->sub_list, sub);
|
|
}
|
|
|
|
render_pgs_subs(pv, in);
|
|
|
|
*buf_in = NULL;
|
|
*buf_out = pv->blend->work(pv->blend, in, &pv->rendered_sub_list, 1);
|
|
|
|
hb_buffer_list_close(&pv->rendered_sub_list);
|
|
|
|
return HB_FILTER_OK;
|
|
}
|
|
|
|
static hb_blend_object_t * hb_blend_init(hb_filter_init_t init, int sub_pix_fmt)
|
|
{
|
|
hb_blend_object_t *blend;
|
|
switch (init.hw_pix_fmt)
|
|
{
|
|
#if defined(__APPLE__)
|
|
case AV_PIX_FMT_VIDEOTOOLBOX:
|
|
blend = &hb_blend_vt;
|
|
break;
|
|
#endif
|
|
default:
|
|
blend = &hb_blend;
|
|
break;
|
|
}
|
|
|
|
hb_blend_object_t *blend_copy = malloc(sizeof(hb_blend_object_t));
|
|
if (blend_copy == NULL)
|
|
{
|
|
hb_error("render_sub: blend malloc failed");
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(blend_copy, blend, sizeof(hb_blend_object_t));
|
|
if (blend_copy->init(blend_copy, init.geometry.width, init.geometry.height,
|
|
init.pix_fmt, init.chroma_location,
|
|
init.color_range, sub_pix_fmt))
|
|
{
|
|
free(blend_copy);
|
|
hb_error("render_sub: blend init failed");
|
|
return NULL;
|
|
}
|
|
|
|
return blend_copy;
|
|
}
|
|
|
|
void hb_blend_close(hb_blend_object_t **_b)
|
|
{
|
|
hb_blend_object_t *b = *_b;
|
|
if (b == NULL)
|
|
{
|
|
return;
|
|
}
|
|
b->close(b);
|
|
free(b);
|
|
*_b = NULL;
|
|
}
|
|
|
|
static int hb_rendersub_init(hb_filter_object_t *filter,
|
|
hb_filter_init_t *init)
|
|
{
|
|
filter->private_data = calloc(1, sizeof(struct hb_filter_private_s));
|
|
if (filter->private_data == NULL)
|
|
{
|
|
hb_error("rendersub: calloc failed");
|
|
return -1;
|
|
}
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
hb_subtitle_t *subtitle;
|
|
|
|
pv->input = *init;
|
|
|
|
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(init->pix_fmt);
|
|
pv->wshift = desc->log2_chroma_w;
|
|
pv->hshift = desc->log2_chroma_h;
|
|
|
|
hb_compute_chroma_smoothing_coefficient(pv->chroma_coeffs,
|
|
init->pix_fmt,
|
|
init->chroma_location);
|
|
|
|
// Find the subtitle we need
|
|
for (int ii = 0; ii < hb_list_count(init->job->list_subtitle); ii++)
|
|
{
|
|
subtitle = hb_list_item(init->job->list_subtitle, ii);
|
|
if (subtitle && subtitle->config.dest == RENDERSUB)
|
|
{
|
|
// Found it
|
|
filter->subtitle = subtitle;
|
|
pv->type = subtitle->source;
|
|
break;
|
|
}
|
|
}
|
|
if (filter->subtitle == NULL)
|
|
{
|
|
hb_log("rendersub: no subtitle marked for burn");
|
|
return 1;
|
|
}
|
|
pv->output = *init;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hb_rendersub_post_init(hb_filter_object_t *filter, hb_job_t *job)
|
|
{
|
|
int ret = 0;
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
|
|
pv->crop[0] = job->crop[0];
|
|
pv->crop[1] = job->crop[1];
|
|
pv->crop[2] = job->crop[2];
|
|
pv->crop[3] = job->crop[3];
|
|
|
|
switch (pv->type)
|
|
{
|
|
case VOBSUB:
|
|
{
|
|
ret = vobsub_post_init(filter, job);
|
|
break;
|
|
}
|
|
|
|
case SSASUB:
|
|
{
|
|
ret = ssa_post_init(filter, job);
|
|
break;
|
|
}
|
|
|
|
case IMPORTSRT:
|
|
case IMPORTSSA:
|
|
case UTF8SUB:
|
|
case TX3GSUB:
|
|
{
|
|
ret = textsub_post_init(filter, job);
|
|
break;
|
|
}
|
|
|
|
case CC608SUB:
|
|
{
|
|
ret = cc608sub_post_init(filter, job);
|
|
break;
|
|
}
|
|
|
|
case DVBSUB:
|
|
case PGSSUB:
|
|
{
|
|
ret = pgssub_post_init(filter, job);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
hb_log("rendersub: unsupported subtitle format %d", pv->type);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (ret > 0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
pv->blend = hb_blend_init(pv->input, pv->pix_fmt_alpha);
|
|
|
|
if (pv->blend == NULL)
|
|
{
|
|
hb_log("rendersub: blend initialization failed");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hb_rendersub_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
switch (pv->type)
|
|
{
|
|
case VOBSUB:
|
|
{
|
|
return vobsub_work(filter, buf_in, buf_out);
|
|
}
|
|
|
|
case SSASUB:
|
|
{
|
|
return ssa_work(filter, buf_in, buf_out);
|
|
}
|
|
|
|
case IMPORTSRT:
|
|
case IMPORTSSA:
|
|
case CC608SUB:
|
|
case UTF8SUB:
|
|
case TX3GSUB:
|
|
{
|
|
return textsub_work(filter, buf_in, buf_out);
|
|
}
|
|
|
|
case DVBSUB:
|
|
case PGSSUB:
|
|
{
|
|
return pgssub_work(filter, buf_in, buf_out);
|
|
}
|
|
|
|
default:
|
|
{
|
|
hb_error("rendersub: unsupported subtitle format %d", pv->type);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void hb_rendersub_close(hb_filter_object_t *filter)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
|
|
if (pv == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pv->sws != NULL)
|
|
{
|
|
sws_freeContext(pv->sws);
|
|
}
|
|
|
|
hb_buffer_list_close(&pv->rendered_sub_list);
|
|
hb_blend_close(&pv->blend);
|
|
|
|
switch (pv->type)
|
|
{
|
|
case VOBSUB:
|
|
{
|
|
vobsub_close(filter);
|
|
} break;
|
|
|
|
case SSASUB:
|
|
{
|
|
ssa_close(filter);
|
|
} break;
|
|
|
|
case IMPORTSRT:
|
|
case IMPORTSSA:
|
|
case CC608SUB:
|
|
case UTF8SUB:
|
|
case TX3GSUB:
|
|
{
|
|
textsub_close(filter);
|
|
} break;
|
|
|
|
case DVBSUB:
|
|
case PGSSUB:
|
|
{
|
|
pgssub_close(filter);
|
|
} break;
|
|
|
|
default:
|
|
{
|
|
hb_error("rendersub: unsupported subtitle format %d", pv->type);
|
|
} break;
|
|
}
|
|
}
|
|
|