mirror of https://github.com/HandBrake/HandBrake
407 lines
12 KiB
C
407 lines
12 KiB
C
/* encavsub.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/encavsub.h"
|
|
#include "handbrake/extradata.h"
|
|
|
|
#define ENC_BUF_DEFAULT_SZ (4 * 1024)
|
|
#define ENC_BUF_MAX_SZ (1024 * 1024)
|
|
|
|
struct hb_encavsub_context_s
|
|
{
|
|
AVCodecContext *context;
|
|
hb_job_t *job;
|
|
hb_subtitle_t *subtitle;
|
|
|
|
// List of subtitle packets to be output by this encoder.
|
|
hb_buffer_list_t list;
|
|
|
|
// avcodec_encode_subtitle encodes to a raw buffer
|
|
// that we must provide. *Most* libav subtitle encoders
|
|
// will return an error (of various sorts: -1, EINVAL,
|
|
// AVERROR_BUFFER_TOO_SMALL) when the buffer is too small.
|
|
// Some just overrun the buffer :( DVB )
|
|
//
|
|
// TODO: sanitize libav return codes and fix DVB
|
|
uint8_t *buffer;
|
|
int buffer_size;
|
|
};
|
|
|
|
struct hb_work_private_s
|
|
{
|
|
hb_encavsub_context_t *ctx;
|
|
};
|
|
|
|
static int realloc_enc_buffer(hb_encavsub_context_t * ctx)
|
|
{
|
|
if (ctx->buffer_size == 0)
|
|
{
|
|
ctx->buffer_size = ENC_BUF_DEFAULT_SZ;
|
|
}
|
|
else if (ctx->buffer_size * 2 < ENC_BUF_MAX_SZ)
|
|
{
|
|
ctx->buffer_size *= 2;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
ctx->buffer = av_realloc(ctx->buffer, ctx->buffer_size);
|
|
if (ctx->buffer == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* encavsubInit
|
|
***********************************************************************
|
|
* Init function for libav subtitle encoding that may be wrapped
|
|
* by HB subtitle encoder
|
|
**********************************************************************/
|
|
hb_encavsub_context_t * encavsubInit(hb_work_object_t *w, hb_job_t *job)
|
|
{
|
|
const AVCodec *codec;
|
|
AVCodecContext *context;
|
|
hb_encavsub_context_t *ctx = calloc(1, sizeof(hb_encavsub_context_t));
|
|
|
|
if (ctx == NULL)
|
|
{
|
|
hb_error("encavsubInit: calloc ctx failed");
|
|
return NULL;
|
|
}
|
|
ctx->job = job;
|
|
ctx->subtitle = w->subtitle;
|
|
|
|
codec = avcodec_find_encoder(w->codec_param);
|
|
if (codec == NULL)
|
|
{
|
|
hb_error("encavsubInit: avcodec_find_encoder failed");
|
|
goto fail;
|
|
}
|
|
context = avcodec_alloc_context3(codec);
|
|
if (context == NULL)
|
|
{
|
|
hb_error("encavsubInit: avcodec_alloc_context3 failed");
|
|
goto fail;
|
|
}
|
|
ctx->context = context;
|
|
context->codec = codec;
|
|
|
|
// TEXT subtitle encoders require SSA header
|
|
context->subtitle_header = av_malloc(ctx->subtitle->extradata->size + 1);
|
|
if (context->subtitle_header == NULL)
|
|
{
|
|
hb_error("encavsubInit: av_malloc subtitle_header failed");
|
|
goto fail;
|
|
}
|
|
memcpy(context->subtitle_header, ctx->subtitle->extradata->bytes,
|
|
ctx->subtitle->extradata->size);
|
|
context->subtitle_header[ctx->subtitle->extradata->size] = '\0';
|
|
context->subtitle_header_size = ctx->subtitle->extradata->size + 1;
|
|
context->time_base = AV_TIME_BASE_Q;
|
|
|
|
// Set encoder opts...
|
|
AVDictionary *av_opts = NULL;
|
|
if (w->codec_param == AV_CODEC_ID_MOV_TEXT)
|
|
{
|
|
char *height = hb_strdup_printf("%d", job->height);
|
|
av_dict_set( &av_opts, "height", height, 0 );
|
|
free(height);
|
|
}
|
|
if (hb_avcodec_open(ctx->context, codec, &av_opts, 0))
|
|
{
|
|
av_dict_free( &av_opts );
|
|
hb_error("encavsubInit: avcodec_open failed");
|
|
goto fail;
|
|
}
|
|
av_dict_free( &av_opts );
|
|
|
|
// avcodec encoder may create subtitle extradata
|
|
hb_data_close(&ctx->subtitle->extradata);
|
|
|
|
if (context->extradata != NULL && context->extradata_size > 0)
|
|
{
|
|
int ret = hb_set_extradata(&ctx->subtitle->extradata,
|
|
context->extradata,
|
|
context->extradata_size);
|
|
if (ret != 0)
|
|
{
|
|
hb_error("encavsubInit: malloc subtitle extradata failed");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (!realloc_enc_buffer(ctx))
|
|
{
|
|
hb_error("encavsubInit: realloc buffer failed");
|
|
goto fail;
|
|
}
|
|
hb_buffer_list_clear(&ctx->list);
|
|
|
|
return ctx;
|
|
|
|
|
|
fail:
|
|
if (ctx != NULL)
|
|
{
|
|
if (ctx->context != NULL)
|
|
{
|
|
avcodec_free_context(&ctx->context);
|
|
}
|
|
}
|
|
free(ctx);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* encavsubClose
|
|
***********************************************************************
|
|
* Close function for libav subtitle encoding that may be wrapped
|
|
* by HB subtitle encoder
|
|
**********************************************************************/
|
|
void encavsubClose(hb_encavsub_context_t *ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
{
|
|
return;
|
|
}
|
|
avcodec_free_context(&ctx->context);
|
|
av_free(ctx->buffer);
|
|
free(ctx);
|
|
}
|
|
|
|
/***********************************************************************
|
|
* encavsubWork
|
|
***********************************************************************
|
|
* Work function for libav subtitle encoding that may be wrapped
|
|
* by HB subtitle encoder
|
|
**********************************************************************/
|
|
int encavsubWork(hb_encavsub_context_t *ctx,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out)
|
|
{
|
|
hb_buffer_t *in = *buf_in;
|
|
AVSubtitle subtitle;
|
|
int num_rects = 1;
|
|
int64_t duration;
|
|
|
|
if (in->s.flags & HB_BUF_FLAG_EOF)
|
|
{
|
|
/* EOF on input stream - send it downstream & say that we're done */
|
|
*buf_in = NULL;
|
|
|
|
*buf_out = in;
|
|
return HB_WORK_DONE;
|
|
}
|
|
|
|
// Create an AVSubtitle from the input buffer
|
|
memset(&subtitle, 0, sizeof(subtitle));
|
|
subtitle.rects = av_mallocz(num_rects * sizeof(subtitle.rects));
|
|
if (subtitle.rects == NULL)
|
|
{
|
|
*buf_out = hb_buffer_eof_init();
|
|
hb_error("encavsubInit: av_mallocz_array failed");
|
|
return HB_WORK_DONE;
|
|
}
|
|
|
|
for (int ii = 0; ii < num_rects; ii++)
|
|
{
|
|
subtitle.rects[ii] = av_mallocz(sizeof(*subtitle.rects[0]));
|
|
if (!subtitle.rects[ii]) {
|
|
avsubtitle_free(&subtitle);
|
|
*buf_out = hb_buffer_eof_init();
|
|
hb_error("encavsubInit: av_mallocz failed");
|
|
return HB_WORK_DONE;
|
|
}
|
|
subtitle.num_rects++;
|
|
}
|
|
|
|
if (ctx->subtitle->format == TEXTSUB)
|
|
{
|
|
subtitle.rects[0]->type = SUBTITLE_ASS;
|
|
subtitle.rects[0]->ass = av_strdup((char *)in->data);
|
|
if (subtitle.rects[0]->ass == NULL)
|
|
{
|
|
avsubtitle_free(&subtitle);
|
|
*buf_out = hb_buffer_eof_init();
|
|
hb_error("encavsubInit: av_strdup failed");
|
|
return HB_WORK_DONE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: bitmap subtitles
|
|
// Must simplify format of hb_buffer_t bitmap representation
|
|
for (int ii = 0; ii < num_rects; ii++)
|
|
{
|
|
subtitle.rects[0]->type = SUBTITLE_BITMAP;
|
|
}
|
|
}
|
|
|
|
subtitle.pts = av_rescale(in->s.start, AV_TIME_BASE, 90000);
|
|
if (in->s.stop != AV_NOPTS_VALUE)
|
|
{
|
|
duration = in->s.stop - in->s.start;
|
|
subtitle.end_display_time = av_rescale(duration, 1000, 90000);
|
|
}
|
|
|
|
int size;
|
|
do
|
|
{
|
|
size = avcodec_encode_subtitle(ctx->context,
|
|
ctx->buffer, ctx->buffer_size, &subtitle);
|
|
if (size < 0)
|
|
{
|
|
if (!realloc_enc_buffer(ctx))
|
|
{
|
|
avsubtitle_free(&subtitle);
|
|
*buf_out = hb_buffer_eof_init();
|
|
hb_error("encavsubInit: realloc failed");
|
|
return HB_WORK_DONE;
|
|
}
|
|
}
|
|
else if (size > 0)
|
|
{
|
|
// Next iteration, flush additional subtitle packets that
|
|
// the encoder might generate
|
|
subtitle.num_rects = 0;
|
|
|
|
hb_buffer_t *out = hb_buffer_init(size);
|
|
memcpy(out->data, ctx->buffer, size);
|
|
out->s.start = av_rescale(subtitle.pts, 90000, AV_TIME_BASE);
|
|
out->s.duration = (int64_t)AV_NOPTS_VALUE;
|
|
if (subtitle.end_display_time > 0)
|
|
{
|
|
duration = av_rescale(subtitle.end_display_time, 90000, 1000);
|
|
out->s.stop = out->s.start + duration;
|
|
out->s.duration = duration;
|
|
}
|
|
hb_buffer_list_append(&ctx->list, out);
|
|
}
|
|
} while (size < 0);
|
|
|
|
subtitle.num_rects = num_rects;
|
|
avsubtitle_free(&subtitle);
|
|
|
|
*buf_out = hb_buffer_list_clear(&ctx->list);
|
|
return HB_WORK_OK;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Init
|
|
***********************************************************************
|
|
* Initialize hb_work_private_t data
|
|
**********************************************************************/
|
|
static int Init(hb_work_object_t *w, hb_job_t *job)
|
|
{
|
|
hb_work_private_t *pv;
|
|
|
|
pv = calloc(1, sizeof(hb_work_private_t));
|
|
if (pv == NULL)
|
|
{
|
|
hb_error("encsubInit: calloc private data failed");
|
|
return 1;
|
|
}
|
|
|
|
pv->ctx = encavsubInit(w, job);
|
|
if (pv->ctx == NULL)
|
|
{
|
|
free(pv);
|
|
return 1;
|
|
}
|
|
w->private_data = pv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Close
|
|
***********************************************************************
|
|
* Free any allocation in hb_work_private_t
|
|
**********************************************************************/
|
|
static void Close(hb_work_object_t *w)
|
|
{
|
|
hb_work_private_t * pv = w->private_data;
|
|
if (pv == NULL)
|
|
{
|
|
return;
|
|
}
|
|
encavsubClose(pv->ctx);
|
|
free(pv);
|
|
w->private_data = NULL;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Work
|
|
***********************************************************************
|
|
* Take an input buffer, send an output buffer
|
|
**********************************************************************/
|
|
static int Work(hb_work_object_t *w, hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out)
|
|
{
|
|
hb_work_private_t *pv = w->private_data;
|
|
|
|
return encavsubWork(pv->ctx, buf_in, buf_out);
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Info
|
|
***********************************************************************
|
|
* Retrieve current info about context initialized during Init
|
|
**********************************************************************/
|
|
static int Info(hb_work_object_t *w, hb_work_info_t *info)
|
|
{
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
// Indicate no info is returned
|
|
return 0;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* BSInfo
|
|
***********************************************************************
|
|
* Retrieve info, does not require Init(), but uses current context
|
|
* if Init has already been called.
|
|
* buf contains stream data to extract info from.
|
|
**********************************************************************/
|
|
static int BSInfo(hb_work_object_t *w, const hb_buffer_t *buf,
|
|
hb_work_info_t *info)
|
|
{
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
// Indicate no info is returned
|
|
return 0;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Flush
|
|
***********************************************************************
|
|
* Reset context without closing, kind of poorly named :(
|
|
**********************************************************************/
|
|
static void Flush(hb_work_object_t *w)
|
|
{
|
|
}
|
|
|
|
hb_work_object_t hb_encavsub =
|
|
{
|
|
.id = WORK_ENCAVSUB,
|
|
.name = "Subtitle encoder (libavcodec)",
|
|
.init = Init,
|
|
.work = Work,
|
|
.close = Close,
|
|
.info = Info,
|
|
.bsinfo = BSInfo,
|
|
.flush = Flush,
|
|
};
|