summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Stebbins <[email protected]>2019-12-08 15:28:07 -0800
committerJohn Stebbins <[email protected]>2020-03-29 08:23:16 -0600
commit9f7c5112a0381e315738c856f2344e2a64be183b (patch)
treefaf1fb6fe664da48a2dcff11a535efad70efeb64
parent7b0b2321c65362b785e149ce8c59a152139b2d15 (diff)
decavsub: add general purpose avcodec subtitle decoder
Currently using it for pgs, srt, and ssa subtitles.
-rw-r--r--libhb/bd.c6
-rw-r--r--libhb/common.c14
-rw-r--r--libhb/decavsub.c650
-rw-r--r--libhb/decpgssub.c509
-rw-r--r--libhb/decsrtsub.c211
-rw-r--r--libhb/decssasub.c91
-rw-r--r--libhb/decutf8sub.c87
-rw-r--r--libhb/handbrake/common.h3
-rw-r--r--libhb/handbrake/decavsub.h22
-rw-r--r--libhb/handbrake/decsrtsub.h16
-rw-r--r--libhb/handbrake/decssasub.h13
-rw-r--r--libhb/handbrake/internal.h3
-rw-r--r--libhb/hb.c3
-rw-r--r--libhb/muxcommon.c1
-rw-r--r--libhb/stream.c76
-rw-r--r--libhb/sync.c4
16 files changed, 835 insertions, 874 deletions
diff --git a/libhb/bd.c b/libhb/bd.c
index c0f957208..72e1c9fb5 100644
--- a/libhb/bd.c
+++ b/libhb/bd.c
@@ -88,7 +88,7 @@ int hb_bd_title_count( hb_bd_t * d )
return d->title_count;
}
-static void add_subtitle(int track, hb_list_t *list_subtitle, BLURAY_STREAM_INFO *bdsub, uint32_t codec)
+static void add_subtitle(int track, hb_list_t *list_subtitle, BLURAY_STREAM_INFO *bdsub, uint32_t codec, uint32_t codec_param)
{
hb_subtitle_t * subtitle;
iso639_lang_t * lang;
@@ -120,6 +120,7 @@ static void add_subtitle(int track, hb_list_t *list_subtitle, BLURAY_STREAM_INFO
subtitle->reg_desc = STR4_TO_UINT32("HDMV");
subtitle->stream_type = bdsub->coding_type;
subtitle->codec = codec;
+ subtitle->codec_param = codec_param;
subtitle->timebase.num = 1;
subtitle->timebase.den = 90000;
@@ -633,7 +634,8 @@ hb_title_t * hb_bd_title_scan( hb_bd_t * d, int tt, uint64_t min_duration )
switch( bdpgs->coding_type )
{
case BLURAY_STREAM_TYPE_SUB_PG:
- add_subtitle(ii, title->list_subtitle, bdpgs, WORK_DECPGSSUB);
+ add_subtitle(ii, title->list_subtitle, bdpgs, WORK_DECAVSUB,
+ AV_CODEC_ID_HDMV_PGS_SUBTITLE);
break;
default:
hb_log( "scan: unknown subtitle pid 0x%x codec 0x%x",
diff --git a/libhb/common.c b/libhb/common.c
index 6452b7bbd..3898fa7db 100644
--- a/libhb/common.c
+++ b/libhb/common.c
@@ -4952,11 +4952,15 @@ int hb_import_subtitle_add( const hb_job_t * job,
return 0;
}
- subtitle->id = (hb_list_count(job->list_subtitle) << 8) |
- HB_SUBTITLE_IMPORT_TAG;
- subtitle->format = TEXTSUB;
- subtitle->source = source;
- subtitle->codec = source == IMPORTSRT ? WORK_DECSRTSUB : WORK_DECSSASUB;
+ subtitle->id = (hb_list_count(job->list_subtitle) << 8) |
+ HB_SUBTITLE_IMPORT_TAG;
+ subtitle->format = TEXTSUB;
+ subtitle->source = source;
+ subtitle->codec = WORK_DECSRTSUB;
+ subtitle->codec = source == IMPORTSRT ? WORK_DECSRTSUB :
+ WORK_DECSSASUB;
+ subtitle->codec_param = source == IMPORTSRT ? AV_CODEC_ID_SUBRIP :
+ AV_CODEC_ID_ASS;
subtitle->timebase.num = 1;
subtitle->timebase.den = 90000;
diff --git a/libhb/decavsub.c b/libhb/decavsub.c
new file mode 100644
index 000000000..24669fe4c
--- /dev/null
+++ b/libhb/decavsub.c
@@ -0,0 +1,650 @@
+/* decavsub.c
+
+ Copyright (c) 2003-2019 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/decavsub.h"
+
+struct hb_avsub_context_s
+{
+ AVCodecContext * context;
+ hb_job_t * job;
+ hb_subtitle_t * subtitle;
+ // For subs, when doing passthru, we don't know if we need a
+ // packet until we have processed several packets. So we cache
+ // all the packets we see until libav returns a subtitle with
+ // the information we need.
+ hb_buffer_list_t list_pass;
+ // List of subtitle packets to be output by this decoder.
+ hb_buffer_list_t list;
+ // XXX: we may occasionally see subtitles with broken timestamps
+ // while this should really get fixed elsewhere,
+ // dropping subtitles should be avoided as much as possible
+ int64_t last_pts;
+ // For PGS subs, we need to pass 'empty' subtitles through (they clear the
+ // display) - when doing forced-only extraction, only pass empty subtitles
+ // through if we've seen a forced sub since the last empty sub
+ uint8_t seen_forced_sub;
+ // if we start encoding partway through the source, we may encounter empty
+ // subtitles before we see any actual subtitle content - discard them
+ uint8_t discard_subtitle;
+};
+
+struct hb_work_private_s
+{
+ hb_avsub_context_t * ctx;
+};
+
+hb_avsub_context_t * decavsubInit( hb_work_object_t * w, hb_job_t * job )
+{
+ hb_avsub_context_t * ctx = calloc( 1, sizeof( hb_avsub_context_t ) );
+
+ if (ctx == NULL)
+ {
+ return NULL;
+ }
+ ctx->discard_subtitle = 1;
+ ctx->seen_forced_sub = 0;
+ ctx->last_pts = AV_NOPTS_VALUE;
+ ctx->job = job;
+ ctx->subtitle = w->subtitle;
+
+ AVCodec * codec = avcodec_find_decoder(ctx->subtitle->codec_param);
+ AVCodecContext * context = avcodec_alloc_context3(codec);
+ context->codec = codec;
+
+
+ hb_buffer_list_clear(&ctx->list);
+ hb_buffer_list_clear(&ctx->list_pass);
+ ctx->context = context;
+ context->pkt_timebase.num = ctx->subtitle->timebase.num;
+ context->pkt_timebase.den = ctx->subtitle->timebase.den;
+
+ // Set decoder opts...
+ AVDictionary * av_opts = NULL;
+ av_dict_set( &av_opts, "sub_text_format", "ass", 0 );
+
+ if (hb_avcodec_open(ctx->context, codec, &av_opts, 0))
+ {
+ av_dict_free( &av_opts );
+ free(ctx);
+ hb_log("decsubInit: avcodec_open failed");
+ return NULL;
+ }
+ av_dict_free( &av_opts );
+
+ if (ctx->subtitle->format == TEXTSUB)
+ {
+ int height = job->title->geometry.height - job->crop[0] - job->crop[1];
+ int width = job->title->geometry.width - job->crop[2] - job->crop[3];
+ switch (ctx->subtitle->codec_param)
+ {
+ case AV_CODEC_ID_ASS:
+ {
+ // Extradata should already be filled in by demux
+ } break;
+
+ case AV_CODEC_ID_EIA_608:
+ {
+ // Mono font for CC
+ hb_subtitle_add_ssa_header(ctx->subtitle, HB_FONT_MONO,
+ .066 * job->title->geometry.height, width, height);
+ } break;
+
+ default:
+ {
+ hb_subtitle_add_ssa_header(ctx->subtitle, HB_FONT_SANS,
+ .066 * job->title->geometry.height, width, height);
+ } break;
+ }
+ }
+ return ctx;
+}
+
+static int decsubInit( 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)
+ {
+ return 1;
+ }
+
+ pv->ctx = decavsubInit(w, job);
+ if (pv->ctx == NULL)
+ {
+ free(pv);
+ return 1;
+ }
+ w->private_data = pv;
+ return 0;
+}
+
+static void make_empty_pgs( hb_buffer_t * buf )
+{
+ hb_buffer_t * b = buf;
+ uint8_t done = 0;
+
+ // Each buffer is composed of 1 or more segments.
+ // Segment header is:
+ // type - 1 byte
+ // length - 2 bytes
+ // We want to modify the presentation segment which is type 0x16
+ //
+ // Note that every pgs display set is required to have a presentation
+ // segment, so we will only have to look at one display set.
+ while ( b && !done )
+ {
+ int ii = 0;
+
+ while (ii + 3 <= b->size)
+ {
+ uint8_t type;
+ int len;
+ int segment_len_pos;
+
+ type = b->data[ii++];
+ segment_len_pos = ii;
+ len = ((int)b->data[ii] << 8) + b->data[ii+1];
+ ii += 2;
+
+ if (type == 0x16 && ii + len <= b->size)
+ {
+ int obj_count;
+ int kk, jj = ii;
+ int obj_start;
+
+ // Skip
+ // video descriptor 5 bytes
+ // composition descriptor 3 bytes
+ // palette update flg 1 byte
+ // palette id ref 1 byte
+ jj += 10;
+
+ // Set number of composition objects to 0
+ obj_count = b->data[jj];
+ b->data[jj] = 0;
+ jj++;
+ obj_start = jj;
+
+ // And remove all the composition objects
+ for (kk = 0; kk < obj_count; kk++)
+ {
+ uint8_t crop;
+
+ crop = b->data[jj + 3];
+ // skip
+ // object id - 2 bytes
+ // window id - 1 byte
+ // object/forced flag - 1 byte
+ // x pos - 2 bytes
+ // y pos - 2 bytes
+ jj += 8;
+ if (crop & 0x80)
+ {
+ // skip
+ // crop x - 2 bytes
+ // crop y - 2 bytes
+ // crop w - 2 bytes
+ // crop h - 2 bytes
+ jj += 8;
+ }
+ }
+ if (jj < b->size)
+ {
+ memmove(b->data + obj_start, b->data + jj, b->size - jj);
+ }
+ b->size = obj_start + ( b->size - jj );
+ done = 1;
+ len = obj_start - (segment_len_pos + 2);
+ b->data[segment_len_pos] = len >> 8;
+ b->data[segment_len_pos+1] = len & 0xff;
+ break;
+ }
+ ii += len;
+ }
+ b = b->next;
+ }
+}
+
+static void make_empty_sub( int source, hb_buffer_list_t * list_pass )
+{
+ switch (source)
+ {
+ case PGSSUB:
+ make_empty_pgs(hb_buffer_list_head(list_pass));
+ break;
+ default:
+ hb_buffer_list_close(list_pass);
+ break;
+ }
+}
+
+int decavsubWork( hb_avsub_context_t * ctx,
+ hb_buffer_t ** buf_in,
+ hb_buffer_t ** buf_out )
+{
+ hb_buffer_t * in = *buf_in;
+
+ if (in->s.flags & HB_BUF_FLAG_EOF)
+ {
+ /* EOF on input stream - send it downstream & say that we're done */
+ *buf_in = NULL;
+ hb_buffer_list_append(&ctx->list, in);
+
+ *buf_out = hb_buffer_list_clear(&ctx->list);
+ return HB_WORK_DONE;
+ }
+
+ if (!ctx->job->indepth_scan &&
+ ctx->subtitle->config.dest == PASSTHRUSUB &&
+ hb_subtitle_can_pass(ctx->subtitle->source, ctx->job->mux))
+ {
+ // Append to buffer list. It will be sent to fifo after we determine
+ // if this is a packet we need.
+ hb_buffer_list_append(&ctx->list_pass, in);
+
+ // We are keeping the buffer, so prevent the filter loop from
+ // deleting it.
+ *buf_in = NULL;
+ }
+
+ AVSubtitle subtitle;
+ memset( &subtitle, 0, sizeof(subtitle) );
+
+ int64_t duration = AV_NOPTS_VALUE;
+ AVPacket avp;
+
+ av_init_packet( &avp );
+ avp.data = in->data;
+ avp.size = in->size;
+ avp.pts = in->s.start;
+ duration = in->s.duration;
+
+ if (duration <= 0 &&
+ in->s.start != AV_NOPTS_VALUE &&
+ in->s.stop != AV_NOPTS_VALUE)
+ {
+ duration = in->s.stop - in->s.start;
+ }
+
+ int has_subtitle = 0;
+
+ while (avp.size > 0)
+ {
+ int usedBytes = avcodec_decode_subtitle2(ctx->context, &subtitle,
+ &has_subtitle, &avp );
+ if (usedBytes < 0)
+ {
+ hb_log("unable to decode subtitle with %d bytes.", avp.size);
+ return HB_WORK_OK;
+ }
+
+ if (usedBytes <= avp.size)
+ {
+ avp.data += usedBytes;
+ avp.size -= usedBytes;
+ }
+ else
+ {
+ avp.size = 0;
+ }
+
+ if (!has_subtitle)
+ {
+ continue;
+ }
+
+ uint8_t forced_sub = 0;
+ uint8_t usable_sub = 0;
+ uint8_t clear_sub = 0;
+
+ // collect subtitle statistics for foreign audio search
+ if (subtitle.num_rects)
+ {
+ ctx->subtitle->hits++;
+ if (subtitle.rects[0]->flags & AV_SUBTITLE_FLAG_FORCED)
+ {
+ forced_sub = 1;
+ ctx->subtitle->forced_hits++;
+ }
+ }
+ else
+ {
+ clear_sub = 1;
+ }
+
+ // Discard leading empty subtitles
+ ctx->discard_subtitle = ctx->discard_subtitle && clear_sub;
+
+ // are we doing Foreign Audio Search? or subtitle needs to be discarded?
+ if (ctx->job->indepth_scan || ctx->discard_subtitle)
+ {
+ avsubtitle_free(&subtitle);
+ continue;
+ }
+
+ // do we need this subtitle?
+ usable_sub =
+ // Need all subs
+ !ctx->subtitle->config.force ||
+ // Need only forced subs
+ forced_sub ||
+ // Need to terminate last forced sub
+ (ctx->seen_forced_sub && clear_sub);
+
+ // do we need to create an empty subtitle?
+ if (ctx->subtitle->config.force && ctx->seen_forced_sub && !usable_sub)
+ {
+ // We are forced-only and need to output this subtitle, but
+ // it's neither forced nor empty.
+ //
+ // If passthru, create an empty subtitle.
+ // Also, flag an empty subtitle for subtitle RENDER.
+ make_empty_sub(ctx->subtitle->source, &ctx->list_pass);
+ usable_sub = clear_sub = 1;
+ }
+
+ if (!usable_sub)
+ {
+ // Discard accumulated passthrough subtitle data
+ hb_buffer_list_close(&ctx->list_pass);
+ avsubtitle_free(&subtitle);
+ continue;
+ }
+
+ // Keep track of forced subs that we may need to manually
+ // terminate with an empty subtitle packet.
+ ctx->seen_forced_sub = forced_sub && !clear_sub;
+
+ int64_t pts = AV_NOPTS_VALUE;
+ hb_buffer_t * out = NULL;
+
+ if (clear_sub)
+ {
+ duration = 0;
+ }
+ else if (subtitle.end_display_time > 0 &&
+ subtitle.end_display_time < UINT32_MAX)
+ {
+ duration = av_rescale(subtitle.end_display_time, 90000, 1000);
+ }
+ if (subtitle.pts != AV_NOPTS_VALUE)
+ {
+ pts = av_rescale(subtitle.pts, 90000, AV_TIME_BASE) +
+ av_rescale(subtitle.start_display_time, 90000, 1000);
+ }
+ else
+ {
+ if (in->s.start >= 0)
+ {
+ pts = in->s.start;
+ }
+ else
+ {
+ // XXX: a broken pts will cause us to drop this subtitle,
+ // which is bad; use a default duration of 3 seconds
+ //
+ // A broken pts is only generated when a subtitle packet
+ // occurs after a discontinuity and before the
+ // next audio or video packet which re-establishes
+ // timing (afaik).
+ if (ctx->last_pts == AV_NOPTS_VALUE)
+ {
+ pts = 0LL;
+ }
+ else
+ {
+ pts = ctx->last_pts + 3 * 90000LL;
+ }
+ hb_log("[warning] decavsub: track %d, invalid PTS",
+ ctx->subtitle->out_track);
+ }
+ }
+ // work around broken timestamps
+ if (pts < ctx->last_pts)
+ {
+ // XXX: this should only happen if the previous pts
+ // was unknown and our 3 second default duration
+ // overshot the next subtitle pts.
+ //
+ // assign a 1 second duration
+ hb_log("decavsub: track %d, non-monotically increasing PTS, last %"PRId64" current %"PRId64"",
+ ctx->subtitle->out_track,
+ ctx->last_pts, pts);
+ pts = ctx->last_pts + 1 * 90000LL;
+ }
+ ctx->last_pts = pts;
+
+ if (ctx->subtitle->format == TEXTSUB)
+ {
+ // TEXTSUB && (PASSTHROUGHSUB || RENDERSUB)
+
+ // Text subtitles are treated the same regardless of
+ // whether we are burning or passing through. They
+ // get translated to SSA
+ if (!clear_sub && subtitle.rects[0]->ass != NULL)
+ {
+ int size = strlen(subtitle.rects[0]->ass) + 1;
+ out = hb_buffer_init(size);
+ strcpy((char*)out->data, subtitle.rects[0]->ass);
+ }
+ else
+ {
+ out = hb_buffer_init(0);
+ out->s.flags = HB_BUF_FLAG_EOS;
+ }
+ out->s.frametype = HB_FRAME_SUBTITLE;
+ out->s.start = pts;
+ if (duration != AV_NOPTS_VALUE)
+ {
+ out->s.stop = pts + duration;
+ out->s.duration = duration;
+ }
+ hb_buffer_list_close(&ctx->list_pass);
+ }
+ else if (ctx->subtitle->config.dest == PASSTHRUSUB &&
+ hb_subtitle_can_pass(ctx->subtitle->source, ctx->job->mux))
+ {
+ // PICTURESUB && PASSTHROUGHSUB
+
+ // subtitles may be spread across multiple packets
+ //
+ // In the MKV container, all segments are found in the same
+ // packet (this is expected by some devices, such as the
+ // WD TV Live). So if there are multiple packets,
+ // merge them.
+ if (hb_buffer_list_count(&ctx->list_pass) == 1)
+ {
+ // packets already merged (e.g. MKV sources)
+ out = hb_buffer_list_clear(&ctx->list_pass);
+ }
+ else
+ {
+ int size = 0;
+ uint8_t * data;
+ hb_buffer_t * b;
+
+ b = hb_buffer_list_head(&ctx->list_pass);
+ while (b != NULL)
+ {
+ size += b->size;
+ b = b->next;
+ }
+
+ out = hb_buffer_init( size );
+ data = out->data;
+ b = hb_buffer_list_head(&ctx->list_pass);
+ while (b != NULL)
+ {
+ memcpy(data, b->data, b->size);
+ data += b->size;
+ b = b->next;
+ }
+ hb_buffer_list_close(&ctx->list_pass);
+
+ out->s = in->s;
+ }
+ out->s.frametype = HB_FRAME_SUBTITLE;
+ out->s.start = pts;
+ }
+ else
+ {
+ // PICTURESUB && RENDERSUB
+ if (!clear_sub)
+ {
+ unsigned ii, x0, y0, x1, y1, w, h;
+
+ x0 = subtitle.rects[0]->x;
+ y0 = subtitle.rects[0]->y;
+ x1 = subtitle.rects[0]->x + subtitle.rects[0]->w;
+ y1 = subtitle.rects[0]->y + subtitle.rects[0]->h;
+
+ // First, find total bounding rectangle
+ for (ii = 1; ii < subtitle.num_rects; ii++)
+ {
+ if (subtitle.rects[ii]->x < x0)
+ x0 = subtitle.rects[ii]->x;
+ if (subtitle.rects[ii]->y < y0)
+ y0 = subtitle.rects[ii]->y;
+ if (subtitle.rects[ii]->x + subtitle.rects[ii]->w > x1)
+ x1 = subtitle.rects[ii]->x + subtitle.rects[ii]->w;
+ if (subtitle.rects[ii]->y + subtitle.rects[ii]->h > y1)
+ y1 = subtitle.rects[ii]->y + subtitle.rects[ii]->h;
+ }
+ w = x1 - x0;
+ h = y1 - y0;
+
+ out = hb_frame_buffer_init(AV_PIX_FMT_YUVA420P, w, h);
+ memset(out->data, 0, out->size);
+
+ out->s.frametype = HB_FRAME_SUBTITLE;
+ out->s.id = in->s.id;
+ out->s.start = pts;
+ out->s.scr_sequence = in->s.scr_sequence;
+ out->f.x = x0;
+ out->f.y = y0;
+ out->f.window_width = ctx->context->width;
+ out->f.window_height = ctx->context->height;
+ for (ii = 0; ii < subtitle.num_rects; ii++)
+ {
+ AVSubtitleRect *rect = subtitle.rects[ii];
+
+ int off_x = rect->x - x0;
+ int off_y = rect->y - y0;
+ uint8_t *lum = out->plane[0].data;
+ uint8_t *chromaU = out->plane[1].data;
+ uint8_t *chromaV = out->plane[2].data;
+ uint8_t *alpha = out->plane[3].data;
+
+ lum += off_y * out->plane[0].stride + off_x;
+ alpha += off_y * out->plane[3].stride + off_x;
+ chromaU += (off_y >> 1) * out->plane[1].stride + (off_x >> 1);
+ chromaV += (off_y >> 1) * out->plane[2].stride + (off_x >> 1);
+
+ int xx, yy;
+ for (yy = 0; yy < rect->h; yy++)
+ {
+ for (xx = 0; xx < rect->w; xx++)
+ {
+ uint32_t argb, yuv;
+ int pixel;
+ uint8_t color;
+
+ pixel = yy * rect->w + xx;
+ color = rect->data[0][pixel];
+ argb = ((uint32_t*)rect->data[1])[color];
+ yuv = hb_rgb2yuv(argb);
+
+ lum[xx] = (yuv >> 16) & 0xff;
+ alpha[xx] = (argb >> 24) & 0xff;
+ if ((xx & 1) == 0 && (yy & 1) == 0)
+ {
+ chromaV[xx>>1] = (yuv >> 8) & 0xff;
+ chromaU[xx>>1] = yuv & 0xff;
+ }
+ }
+ lum += out->plane[0].stride;
+ if ((yy & 1) == 0)
+ {
+ chromaU += out->plane[1].stride;
+ chromaV += out->plane[2].stride;
+ }
+ alpha += out->plane[3].stride;
+ }
+ }
+ hb_buffer_list_append(&ctx->list, out);
+ out = NULL;
+ }
+ else
+ {
+ out = hb_buffer_init( 0 );
+
+ out->s.frametype = HB_FRAME_SUBTITLE;
+ out->s.flags = HB_BUF_FLAG_EOS;
+ out->s.id = in->s.id;
+ out->s.start = pts;
+ out->s.stop = pts;
+ out->s.scr_sequence = in->s.scr_sequence;
+ out->f.x = 0;
+ out->f.y = 0;
+ out->f.width = 0;
+ out->f.height = 0;
+ }
+ }
+ hb_buffer_list_append(&ctx->list, out);
+ avsubtitle_free(&subtitle);
+ }
+
+ *buf_out = hb_buffer_list_clear(&ctx->list);
+ return HB_WORK_OK;
+}
+
+static int decsubWork( hb_work_object_t * w,
+ hb_buffer_t ** buf_in,
+ hb_buffer_t ** buf_out )
+{
+ hb_work_private_t * pv = w->private_data;
+
+ return decavsubWork(pv->ctx, buf_in, buf_out );
+}
+
+void decavsubClose( hb_avsub_context_t * ctx )
+{
+ if (ctx == NULL)
+ {
+ return;
+ }
+ hb_buffer_list_close(&ctx->list_pass);
+ avcodec_flush_buffers(ctx->context);
+ avcodec_free_context(&ctx->context);
+ free(ctx);
+}
+
+static void decsubClose( hb_work_object_t * w )
+{
+ hb_work_private_t * pv = w->private_data;
+ if (pv == NULL)
+ {
+ return;
+ }
+ decavsubClose(pv->ctx);
+ free(pv);
+ w->private_data = NULL;
+}
+
+hb_work_object_t hb_decavsub =
+{
+ .id = WORK_DECAVSUB,
+ .name = "Subtitle decoder (libavcodec)",
+ .init = decsubInit,
+ .work = decsubWork,
+ .close = decsubClose,
+};
diff --git a/libhb/decpgssub.c b/libhb/decpgssub.c
deleted file mode 100644
index 3f8a5600d..000000000
--- a/libhb/decpgssub.c
+++ /dev/null
@@ -1,509 +0,0 @@
-/* decpgssub.c
-
- Copyright (c) 2003-2020 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"
-
-struct hb_work_private_s
-{
- AVCodecContext * context;
- hb_job_t * job;
- // For PGS subs, when doing passthru, we don't know if we need a
- // packet until we have processed several packets. So we cache
- // all the packets we see until libav returns a subtitle with
- // the information we need.
- hb_buffer_list_t list_pass;
- // It is possible for multiple subtitles to be encapsulated in
- // one packet. This won't happen for PGS subs, but may for other
- // types of subtitles. Since I plan to generalize this code to handle
- // other than PGS, we will need to keep a list of all subtitles seen
- // while parsing an input packet.
- hb_buffer_list_t list;
- // XXX: we may occasionally see subtitles with broken timestamps
- // while this should really get fixed elsewhere,
- // dropping subtitles should be avoided as much as possible
- int64_t last_pts;
- // for PGS subs, we need to pass 'empty' subtitles through (they clear the
- // display) - when doing forced-only extraction, only pass empty subtitles
- // through if we've seen a forced sub and haven't seen any empty sub since
- uint8_t seen_forced_sub;
- // if we start encoding partway through the source, we may encounter empty
- // subtitles before we see any actual subtitle content - discard them
- uint8_t discard_subtitle;
-};
-
-static int decsubInit( hb_work_object_t * w, hb_job_t * job )
-{
- AVCodec *codec = avcodec_find_decoder( AV_CODEC_ID_HDMV_PGS_SUBTITLE );
- AVCodecContext *context = avcodec_alloc_context3( codec );
- context->codec = codec;
-
- hb_work_private_t * pv;
- pv = calloc( 1, sizeof( hb_work_private_t ) );
- w->private_data = pv;
-
- hb_buffer_list_clear(&pv->list);
- hb_buffer_list_clear(&pv->list_pass);
- pv->discard_subtitle = 1;
- pv->seen_forced_sub = 0;
- pv->last_pts = AV_NOPTS_VALUE;
- pv->context = context;
- context->pkt_timebase.num = w->subtitle->timebase.num;
- context->pkt_timebase.den = w->subtitle->timebase.den;
- pv->job = job;
-
- // Set decoder opts...
- AVDictionary * av_opts = NULL;
- // e.g. av_dict_set( &av_opts, "refcounted_frames", "1", 0 );
-
- if (hb_avcodec_open(pv->context, codec, &av_opts, 0))
- {
- av_dict_free( &av_opts );
- hb_log("decsubInit: avcodec_open failed");
- return 1;
- }
- av_dict_free( &av_opts );
-
-
- return 0;
-}
-
-static void make_empty_pgs( hb_buffer_t * buf )
-{
- hb_buffer_t * b = buf;
- uint8_t done = 0;
-
- // Each buffer is composed of 1 or more segments.
- // Segment header is:
- // type - 1 byte
- // length - 2 bytes
- // We want to modify the presentation segment which is type 0x16
- //
- // Note that every pgs display set is required to have a presentation
- // segment, so we will only have to look at one display set.
- while ( b && !done )
- {
- int ii = 0;
-
- while (ii + 3 <= b->size)
- {
- uint8_t type;
- int len;
- int segment_len_pos;
-
- type = b->data[ii++];
- segment_len_pos = ii;
- len = ((int)b->data[ii] << 8) + b->data[ii+1];
- ii += 2;
-
- if (type == 0x16 && ii + len <= b->size)
- {
- int obj_count;
- int kk, jj = ii;
- int obj_start;
-
- // Skip
- // video descriptor 5 bytes
- // composition descriptor 3 bytes
- // palette update flg 1 byte
- // palette id ref 1 byte
- jj += 10;
-
- // Set number of composition objects to 0
- obj_count = b->data[jj];
- b->data[jj] = 0;
- jj++;
- obj_start = jj;
-
- // And remove all the composition objects
- for (kk = 0; kk < obj_count; kk++)
- {
- uint8_t crop;
-
- crop = b->data[jj + 3];
- // skip
- // object id - 2 bytes
- // window id - 1 byte
- // object/forced flag - 1 byte
- // x pos - 2 bytes
- // y pos - 2 bytes
- jj += 8;
- if (crop & 0x80)
- {
- // skip
- // crop x - 2 bytes
- // crop y - 2 bytes
- // crop w - 2 bytes
- // crop h - 2 bytes
- jj += 8;
- }
- }
- if (jj < b->size)
- {
- memmove(b->data + obj_start, b->data + jj, b->size - jj);
- }
- b->size = obj_start + ( b->size - jj );
- done = 1;
- len = obj_start - (segment_len_pos + 2);
- b->data[segment_len_pos] = len >> 8;
- b->data[segment_len_pos+1] = len & 0xff;
- break;
- }
- ii += len;
- }
- b = b->next;
- }
-}
-
-static int decsubWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
- hb_buffer_t ** buf_out )
-{
- hb_work_private_t * pv = w->private_data;
- hb_buffer_t * in = *buf_in;
-
- if (in->s.flags & HB_BUF_FLAG_EOF)
- {
- /* EOF on input stream - send it downstream & say that we're done */
- *buf_in = NULL;
- hb_buffer_list_append(&pv->list, in);
-
- *buf_out = hb_buffer_list_clear(&pv->list);
- return HB_WORK_DONE;
- }
-
- if ( !pv->job->indepth_scan &&
- w->subtitle->config.dest == PASSTHRUSUB &&
- hb_subtitle_can_pass( PGSSUB, pv->job->mux ) )
- {
- // Append to buffer list. It will be sent to fifo after we determine
- // if this is a packet we need.
- hb_buffer_list_append(&pv->list_pass, in);
-
- // We are keeping the buffer, so prevent the filter loop from
- // deleting it.
- *buf_in = NULL;
- }
-
- AVSubtitle subtitle;
- memset( &subtitle, 0, sizeof(subtitle) );
-
- AVPacket avp;
- av_init_packet( &avp );
- avp.data = in->data;
- avp.size = in->size;
- avp.pts = in->s.start;
-
- int has_subtitle = 0;
-
- do
- {
- int usedBytes = avcodec_decode_subtitle2( pv->context, &subtitle, &has_subtitle, &avp );
- if (usedBytes < 0)
- {
- hb_log("unable to decode subtitle with %d bytes.", avp.size);
- return HB_WORK_OK;
- }
-
- if (usedBytes <= avp.size)
- {
- avp.data += usedBytes;
- avp.size -= usedBytes;
- }
- else
- {
- avp.size = 0;
- }
-
- /* Subtitles are "usable" if:
- * 1. FFmpeg returned a subtitle (has_subtitle) AND
- * 2. we're not doing Foreign Audio Search (!pv->job->indepth_scan) AND
- * 3. the sub is non-empty or we've seen one such sub before (!pv->discard_subtitle)
- * For forced-only extraction, usable subtitles also need to:
- * a. be forced (subtitle.rects[0]->flags & AV_SUBTITLE_FLAG_FORCED) OR
- * b. follow a forced sub (pv->seen_forced_sub) */
- uint8_t forced_sub = 0;
- uint8_t useable_sub = 0;
- uint8_t clear_subtitle = 0;
-
- if (has_subtitle)
- {
- // subtitle statistics
- if (subtitle.num_rects)
- {
- w->subtitle->hits++;
- if (subtitle.rects[0]->flags & AV_SUBTITLE_FLAG_FORCED)
- {
- forced_sub = 1;
- w->subtitle->forced_hits++;
- }
- }
- else
- {
- clear_subtitle = 1;
- }
- // are we doing Foreign Audio Search?
- if (!pv->job->indepth_scan)
- {
- // do we want to discard this subtitle?
- pv->discard_subtitle = pv->discard_subtitle && clear_subtitle;
- // do we need this subtitle?
- useable_sub = (!pv->discard_subtitle &&
- (!w->subtitle->config.force ||
- forced_sub || pv->seen_forced_sub));
- // do we need to create an empty subtitle?
- if (w->subtitle->config.force &&
- useable_sub && !forced_sub && !clear_subtitle)
- {
- // We are forced-only and need to output this subtitle, but
- // it's neither forced nor empty.
- //
- // If passthru, create an empty subtitle.
- // Also, flag an empty subtitle for subtitle RENDER.
- make_empty_pgs(hb_buffer_list_head(&pv->list_pass));
- clear_subtitle = 1;
- }
- // is the subtitle forced?
- pv->seen_forced_sub = forced_sub;
- }
- }
-
- if (useable_sub)
- {
- int64_t pts = AV_NOPTS_VALUE;
- hb_buffer_t * out = NULL;
-
- if (subtitle.pts != AV_NOPTS_VALUE)
- {
- pts = av_rescale(subtitle.pts, 90000, AV_TIME_BASE);
- }
- else
- {
- if (in->s.start >= 0)
- {
- pts = in->s.start;
- }
- else
- {
- // XXX: a broken pts will cause us to drop this subtitle,
- // which is bad; use a default duration of 3 seconds
- //
- // A broken pts is only generated when a pgs packet
- // occurs after a discontinuity and before the
- // next audio or video packet which re-establishes
- // timing (afaik).
- if (pv->last_pts == AV_NOPTS_VALUE)
- {
- pts = 0LL;
- }
- else
- {
- pts = pv->last_pts + 3 * 90000LL;
- }
- hb_log("[warning] decpgssub: track %d, invalid PTS",
- w->subtitle->out_track);
- }
- }
- // work around broken timestamps
- if (pts < pv->last_pts)
- {
- // XXX: this should only happen if the previous pts
- // was unknown and our 3 second default duration
- // overshot the next pgs pts.
- //
- // assign a 1 second duration
- hb_log("decpgssub: track %d, non-monotically increasing PTS, last %"PRId64" current %"PRId64"",
- w->subtitle->out_track,
- pv->last_pts, pts);
- pts = pv->last_pts + 1 * 90000LL;
- }
- pv->last_pts = pts;
-
- if ( w->subtitle->config.dest == PASSTHRUSUB &&
- hb_subtitle_can_pass( PGSSUB, pv->job->mux ) )
- {
- /* PGS subtitles are spread across multiple packets,
- * 1 per segment.
- *
- * In the MKV container, all segments are found in the same
- * packet (this is expected by some devices, such as the
- * WD TV Live). So if there are multiple packets,
- * merge them. */
- if (hb_buffer_list_count(&pv->list_pass) == 1)
- {
- // packets already merged (e.g. MKV sources)
- out = hb_buffer_list_clear(&pv->list_pass);
- }
- else
- {
- int size = 0;
- uint8_t * data;
- hb_buffer_t * b;
-
- b = hb_buffer_list_head(&pv->list_pass);
- while (b != NULL)
- {
- size += b->size;
- b = b->next;
- }
-
- out = hb_buffer_init( size );
- data = out->data;
- b = hb_buffer_list_head(&pv->list_pass);
- while (b != NULL)
- {
- memcpy(data, b->data, b->size);
- data += b->size;
- b = b->next;
- }
- hb_buffer_list_close(&pv->list_pass);
-
- out->s = in->s;
- }
- out->s.frametype = HB_FRAME_SUBTITLE;
- out->s.renderOffset = AV_NOPTS_VALUE;
- out->s.stop = AV_NOPTS_VALUE;
- out->s.start = pts;
- }
- else
- {
- if (!clear_subtitle)
- {
- unsigned ii, x0, y0, x1, y1, w, h;
-
- x0 = subtitle.rects[0]->x;
- y0 = subtitle.rects[0]->y;
- x1 = subtitle.rects[0]->x + subtitle.rects[0]->w;
- y1 = subtitle.rects[0]->y + subtitle.rects[0]->h;
-
- // First, find total bounding rectangle
- for (ii = 1; ii < subtitle.num_rects; ii++)
- {
- if (subtitle.rects[ii]->x < x0)
- x0 = subtitle.rects[ii]->x;
- if (subtitle.rects[ii]->y < y0)
- y0 = subtitle.rects[ii]->y;
- if (subtitle.rects[ii]->x + subtitle.rects[ii]->w > x1)
- x1 = subtitle.rects[ii]->x + subtitle.rects[ii]->w;
- if (subtitle.rects[ii]->y + subtitle.rects[ii]->h > y1)
- y1 = subtitle.rects[ii]->y + subtitle.rects[ii]->h;
- }
- w = x1 - x0;
- h = y1 - y0;
-
- out = hb_frame_buffer_init(AV_PIX_FMT_YUVA420P, w, h);
- memset(out->data, 0, out->size);
-
- out->s.frametype = HB_FRAME_SUBTITLE;
- out->s.id = in->s.id;
- out->s.start = pts;
- out->s.stop = AV_NOPTS_VALUE;
- out->s.renderOffset = AV_NOPTS_VALUE;
- out->s.scr_sequence = in->s.scr_sequence;
- out->f.x = x0;
- out->f.y = y0;
- out->f.window_width = pv->context->width;
- out->f.window_height = pv->context->height;
- for (ii = 0; ii < subtitle.num_rects; ii++)
- {
- AVSubtitleRect *rect = subtitle.rects[ii];
-
- int off_x = rect->x - x0;
- int off_y = rect->y - y0;
- uint8_t *lum = out->plane[0].data;
- uint8_t *chromaU = out->plane[1].data;
- uint8_t *chromaV = out->plane[2].data;
- uint8_t *alpha = out->plane[3].data;
-
- lum += off_y * out->plane[0].stride + off_x;
- alpha += off_y * out->plane[3].stride + off_x;
- chromaU += (off_y >> 1) * out->plane[1].stride + (off_x >> 1);
- chromaV += (off_y >> 1) * out->plane[2].stride + (off_x >> 1);
-
- int xx, yy;
- for (yy = 0; yy < rect->h; yy++)
- {
- for (xx = 0; xx < rect->w; xx++)
- {
- uint32_t argb, yuv;
- int pixel;
- uint8_t color;
-
- pixel = yy * rect->w + xx;
- color = rect->data[0][pixel];
- argb = ((uint32_t*)rect->data[1])[color];
- yuv = hb_rgb2yuv(argb);
-
- lum[xx] = (yuv >> 16) & 0xff;
- alpha[xx] = (argb >> 24) & 0xff;
- if ((xx & 1) == 0 && (yy & 1) == 0)
- {
- chromaV[xx>>1] = (yuv >> 8) & 0xff;
- chromaU[xx>>1] = yuv & 0xff;
- }
- }
- lum += out->plane[0].stride;
- if ((yy & 1) == 0)
- {
- chromaU += out->plane[1].stride;
- chromaV += out->plane[2].stride;
- }
- alpha += out->plane[3].stride;
- }
- }
- hb_buffer_list_append(&pv->list, out);
- out = NULL;
- }
- else
- {
- out = hb_buffer_init( 0 );
-
- out->s.frametype = HB_FRAME_SUBTITLE;
- out->s.flags = HB_BUF_FLAG_EOS;
- out->s.id = in->s.id;
- out->s.start = pts;
- out->s.stop = pts;
- out->s.renderOffset = AV_NOPTS_VALUE;
- out->s.scr_sequence = in->s.scr_sequence;
- out->f.x = 0;
- out->f.y = 0;
- out->f.width = 0;
- out->f.height = 0;
- }
- }
- hb_buffer_list_append(&pv->list, out);
- }
- else if (has_subtitle)
- {
- hb_buffer_list_close(&pv->list_pass);
- }
- if (has_subtitle)
- {
- avsubtitle_free(&subtitle);
- }
- } while (avp.size > 0);
-
- *buf_out = hb_buffer_list_clear(&pv->list);
- return HB_WORK_OK;
-}
-
-static void decsubClose( hb_work_object_t * w )
-{
- hb_work_private_t * pv = w->private_data;
- avcodec_flush_buffers( pv->context );
- avcodec_free_context( &pv->context );
-}
-
-hb_work_object_t hb_decpgssub =
-{
- WORK_DECPGSSUB,
- "PGS decoder",
- decsubInit,
- decsubWork,
- decsubClose
-};
diff --git a/libhb/decsrtsub.c b/libhb/decsrtsub.c
index 2389b19e1..df99d5f0d 100644
--- a/libhb/decsrtsub.c
+++ b/libhb/decsrtsub.c
@@ -14,7 +14,7 @@
#include <errno.h>
#include "handbrake/handbrake.h"
#include "handbrake/colormap.h"
-#include "handbrake/decsrtsub.h"
+#include "handbrake/decavsub.h"
struct start_and_end {
unsigned long start, end;
@@ -40,6 +40,7 @@ typedef struct srt_entry_s {
*/
struct hb_work_private_s
{
+ hb_avsub_context_t * ctx;
hb_job_t * job;
FILE * file;
char buf[1024];
@@ -62,155 +63,6 @@ struct hb_work_private_s
int line; // SSA line number
};
-static char* srt_markup_to_ssa(char *srt, int *len)
-{
- char terminator;
- char color[40];
- uint32_t rgb;
-
- *len = 0;
- if (srt[0] != '<' && srt[0] != '{')
- return NULL;
-
- if (srt[0] == '<')
- terminator = '>';
- else
- terminator = '}';
-
- if (srt[1] == 'i' && srt[2] == terminator)
- {
- *len = 3;
- return hb_strdup_printf("{\\i1}");
- }
- else if (srt[1] == 'b' && srt[2] == terminator)
- {
- *len = 3;
- return hb_strdup_printf("{\\b1}");
- }
- else if (srt[1] == 'u' && srt[2] == terminator)
- {
- *len = 3;
- return hb_strdup_printf("{\\u1}");
- }
- else if (srt[1] == '/' && srt[2] == 'i' && srt[3] == terminator)
- {
- *len = 4;
- return hb_strdup_printf("{\\i0}");
- }
- else if (srt[1] == '/' && srt[2] == 'b' && srt[3] == terminator)
- {
- *len = 4;
- return hb_strdup_printf("{\\b0}");
- }
- else if (srt[1] == '/' && srt[2] == 'u' && srt[3] == terminator)
- {
- *len = 4;
- return hb_strdup_printf("{\\u0}");
- }
- else if (srt[0] == '<' && !strncmp(srt + 1, "font", 4))
- {
- int match;
- match = sscanf(srt + 1, "font color=\"%39[^\"]\">", color);
- if (match != 1)
- {
- return NULL;
- }
- while (srt[*len] != '>') (*len)++;
- (*len)++;
- if (color[0] == '#')
- rgb = strtol(color + 1, NULL, 16);
- else
- rgb = hb_rgb_lookup_by_name(color);
- return hb_strdup_printf("{\\1c&H%X&}", HB_RGB_TO_BGR(rgb));
- }
- else if (srt[0] == '<' && srt[1] == '/' && !strncmp(srt + 2, "font", 4) &&
- srt[6] == '>')
- {
- *len = 7;
- return hb_strdup_printf("{\\1c&HFFFFFF&}");
- }
-
- return NULL;
-}
-
-void hb_srt_to_ssa(hb_buffer_t *sub_in, int line)
-{
- if (sub_in->size == 0)
- return;
-
- // null terminate input if not already terminated
- if (sub_in->data[sub_in->size-1] != 0)
- {
- hb_buffer_realloc(sub_in, ++sub_in->size);
- sub_in->data[sub_in->size - 1] = 0;
- }
- char * srt = (char*)sub_in->data;
- // SSA markup expands a little over SRT, so allocate a bit of extra
- // space. More will be realloc'd if needed.
- hb_buffer_t * sub = hb_buffer_init(sub_in->size + 80);
- char * ssa, *ssa_markup;
- int skip, len, pos, ii;
-
- // Exchange data between input sub and new ssa_sub
- // After this, sub_in contains ssa data
- hb_buffer_swap_copy(sub_in, sub);
- ssa = (char*)sub_in->data;
-
- sprintf((char*)sub_in->data, "%d,,Default,,0,0,0,,", line);
- pos = strlen((char*)sub_in->data);
-
- ii = 0;
- while (srt[ii] != '\0')
- {
- if ((ssa_markup = srt_markup_to_ssa(srt + ii, &skip)) != NULL)
- {
- len = strlen(ssa_markup);
- hb_buffer_realloc(sub_in, pos + len + 1);
- // After realloc, sub_in->data may change
- ssa = (char*)sub_in->data;
- sprintf(ssa + pos, "%s", ssa_markup);
- free(ssa_markup);
- pos += len;
- ii += skip;
- }
- else
- {
- hb_buffer_realloc(sub_in, pos + 4);
- // After realloc, sub_in->data may change
- ssa = (char*)sub_in->data;
- if (srt[ii] == '\r')
- {
- if (srt[ii + 1] == '\n')
- {
- ii++;
- }
- if (srt[ii + 1] != 0)
- {
- ssa[pos++] = '\\';
- ssa[pos++] = 'N';
- }
- ii++;
- }
- else if (srt[ii] == '\n')
- {
- if (srt[ii + 1] != 0)
- {
- ssa[pos++] = '\\';
- ssa[pos++] = 'N';
- }
- ii++;
- }
- else
- {
- ssa[pos++] = srt[ii++];
- }
- }
- }
- ssa[pos] = '\0';
- sub_in->size = pos + 1;
- hb_buffer_close(&sub);
-}
-
static int
read_time_from_string( const char* timeString, struct start_and_end *result )
{
@@ -363,10 +215,25 @@ static hb_buffer_t *srt_read( hb_work_private_t *pv )
char line_buffer[1024];
int reprocess = 0, resync = 0;
- if( !pv->file )
+ if (!pv->file)
{
+ return hb_buffer_eof_init();
+ }
+
+ if (pv->job->reader_pts_offset == AV_NOPTS_VALUE)
+ {
+ // We need to wait for reader to initialize it's pts offset so that
+ // we know where to start reading SRTs.
return NULL;
}
+ if (pv->start_time == AV_NOPTS_VALUE)
+ {
+ pv->start_time = pv->job->reader_pts_offset;
+ if (pv->job->pts_to_stop > 0)
+ {
+ pv->stop_time = pv->job->pts_to_start + pv->job->pts_to_stop;
+ }
+ }
while( reprocess || get_line( pv, line_buffer, sizeof( line_buffer ) ) )
{
@@ -572,7 +439,7 @@ static hb_buffer_t *srt_read( hb_work_private_t *pv )
{
hb_deep_log( 3, "Discarding SRT at time start %"PRId64", stop %"PRId64, start_time, stop_time);
memset( &pv->current_entry, 0, sizeof( srt_entry_t ) );
- return NULL;
+ return hb_buffer_eof_init();
}
for (q = p = pv->current_entry.text; *p != '\0'; p++)
@@ -613,7 +480,7 @@ static hb_buffer_t *srt_read( hb_work_private_t *pv )
return buffer;
}
- return NULL;
+ return hb_buffer_eof_init();
}
static int decsrtInit( hb_work_object_t * w, hb_job_t * job )
@@ -627,6 +494,11 @@ static int decsrtInit( hb_work_object_t * w, hb_job_t * job )
{
goto fail;
}
+ pv->ctx = decavsubInit(w, job);
+ if (pv->ctx == NULL)
+ {
+ goto fail;
+ }
w->private_data = pv;
@@ -701,6 +573,7 @@ static int decsrtInit( hb_work_object_t * w, hb_job_t * job )
fail:
if (pv != NULL)
{
+ decavsubClose(pv->ctx);
if (pv->iconv_context != (iconv_t) -1)
{
iconv_close(pv->iconv_context);
@@ -719,33 +592,21 @@ static int decsrtWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
hb_buffer_t ** buf_out )
{
hb_work_private_t * pv = w->private_data;
- hb_buffer_t * out = NULL;
+ hb_buffer_t * in;
+ int result;
- if (pv->job->reader_pts_offset == AV_NOPTS_VALUE)
+ in = srt_read( pv );
+ if (in == NULL)
{
- // We need to wait for reader to initialize it's pts offset so that
- // we know where to start reading SRTs.
- *buf_out = NULL;
return HB_WORK_OK;
}
- if (pv->start_time == AV_NOPTS_VALUE)
- {
- pv->start_time = pv->job->reader_pts_offset;
- if (pv->job->pts_to_stop > 0)
- {
- pv->stop_time = pv->job->pts_to_start + pv->job->pts_to_stop;
- }
- }
- out = srt_read( pv );
- if (out != NULL)
+
+ result = decavsubWork(pv->ctx, &in, buf_out);
+ if (in != NULL)
{
- hb_srt_to_ssa(out, ++pv->line);
- *buf_out = out;
- return HB_WORK_OK;
- } else {
- *buf_out = hb_buffer_eof_init();
- return HB_WORK_DONE;
+ hb_buffer_close(&in);
}
+ return result;
}
static void decsrtClose( hb_work_object_t * w )
@@ -753,10 +614,12 @@ static void decsrtClose( hb_work_object_t * w )
hb_work_private_t * pv = w->private_data;
if (pv != NULL)
{
+ decavsubClose(pv->ctx);
fclose( pv->file );
iconv_close(pv->iconv_context);
free( w->private_data );
}
+ w->private_data = NULL;
}
hb_work_object_t hb_decsrtsub =
diff --git a/libhb/decssasub.c b/libhb/decssasub.c
index 9dc4e1795..2e8e2ae05 100644
--- a/libhb/decssasub.c
+++ b/libhb/decssasub.c
@@ -29,11 +29,12 @@
#include "handbrake/handbrake.h"
#include <ass/ass.h>
-#include "handbrake/decssasub.h"
+#include "handbrake/decavsub.h"
#include "handbrake/colormap.h"
struct hb_work_private_s
{
+ hb_avsub_context_t * ctx;
hb_job_t * job;
hb_subtitle_t * subtitle;
@@ -117,25 +118,37 @@ static int decssaInit( hb_work_object_t * w, hb_job_t * job )
int ii;
pv = calloc( 1, sizeof( hb_work_private_t ) );
+ if (pv == NULL)
+ {
+ goto fail;
+ }
w->private_data = pv;
+ pv->ctx = decavsubInit(w, job);
+ if (pv->ctx == NULL)
+ {
+ goto fail;
+ }
pv->job = job;
pv->subtitle = w->subtitle;
- if (w->fifo_in == NULL && pv->subtitle->config.src_filename != NULL)
+ if (pv->subtitle->config.src_filename == NULL)
{
- pv->file = hb_fopen(pv->subtitle->config.src_filename, "r");
- if(pv->file == NULL)
- {
- hb_error("Could not open the SSA subtitle file '%s'\n",
- pv->subtitle->config.src_filename);
- goto fail;
- }
+ hb_error("No SSA subtitle file specified");
+ goto fail;
+ }
- // Read SSA header and store in subtitle extradata
- if (extradataInit(pv))
- {
- goto fail;
- }
+ pv->file = hb_fopen(pv->subtitle->config.src_filename, "r");
+ if(pv->file == NULL)
+ {
+ hb_error("Could not open the SSA subtitle file '%s'\n",
+ pv->subtitle->config.src_filename);
+ goto fail;
+ }
+
+ // Read SSA header and store in subtitle extradata
+ if (extradataInit(pv))
+ {
+ goto fail;
}
/*
@@ -179,6 +192,7 @@ static int decssaInit( hb_work_object_t * w, hb_job_t * job )
fail:
if (pv != NULL)
{
+ decavsubClose(pv->ctx);
if (pv->file != NULL)
{
fclose(pv->file);
@@ -358,6 +372,11 @@ static hb_buffer_t * ssa_read( hb_work_private_t * pv )
{
hb_buffer_t * out;
+ if (!pv->file)
+ {
+ return hb_buffer_eof_init();
+ }
+
if (pv->job->reader_pts_offset == AV_NOPTS_VALUE)
{
// We need to wait for reader to initialize it's pts offset so that
@@ -405,41 +424,33 @@ static int decssaWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
hb_buffer_t ** buf_out )
{
hb_work_private_t * pv = w->private_data;
- hb_buffer_t * in = *buf_in;
+ hb_buffer_t * in;
+ int result;
- *buf_in = NULL;
- *buf_out = NULL;
- if (in == NULL && pv->file != NULL)
+ in = ssa_read(pv);
+ if (in == NULL)
{
- in = ssa_read(pv);
- if (in == NULL)
- {
- return HB_WORK_OK;
- }
+ return HB_WORK_OK;
}
- *buf_out = in;
- if (in->s.flags & HB_BUF_FLAG_EOF)
+
+ result = decavsubWork(pv->ctx, &in, buf_out);
+ if (in != NULL)
{
- return HB_WORK_DONE;
+ hb_buffer_close(&in);
}
-
- // Not much to do here. ffmpeg already supplies SSA subtitles in the
- // required matroska packet format.
- //
- // We require string termination of the buffer
- hb_buffer_realloc(in, ++in->size);
- in->data[in->size - 1] = '\0';
-
-#if SSA_VERBOSE_PACKETS
- printf("\nPACKET(%"PRId64",%"PRId64"): %.*s\n", in->s.start/90, in->s.stop/90, in->size, in->data);
-#endif
-
- return HB_WORK_OK;
+ return result;
}
static void decssaClose( hb_work_object_t * w )
{
- free( w->private_data );
+ hb_work_private_t * pv = w->private_data;
+ if (pv != NULL)
+ {
+ decavsubClose(pv->ctx);
+ fclose(pv->file);
+ free(pv);
+ }
+ w->private_data = NULL;
}
hb_work_object_t hb_decssasub =
diff --git a/libhb/decutf8sub.c b/libhb/decutf8sub.c
deleted file mode 100644
index 84428f0d0..000000000
--- a/libhb/decutf8sub.c
+++ /dev/null
@@ -1,87 +0,0 @@
-/* decutf8sub.c
-
- Copyright (c) 2003-2020 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
- */
-
-/*
- * Decoder for UTF-8 subtitles obtained from file input-sources.
- *
- * Input and output packet format is UTF-8 encoded text,
- * with limited HTML-style markup (only <b>, <i>, and <u>).
- *
- * @author David Foster (davidfstr)
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include "handbrake/handbrake.h"
-#include "handbrake/decsrtsub.h"
-
-struct hb_work_private_s
-{
- int line; // SSA line number
-};
-
-static int decutf8Init(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)
- return 1;
- w->private_data = pv;
-
- // Generate generic SSA Script Info.
- int height = job->title->geometry.height - job->crop[0] - job->crop[1];
- int width = job->title->geometry.width - job->crop[2] - job->crop[3];
- hb_subtitle_add_ssa_header(w->subtitle, HB_FONT_SANS,
- .066 * job->title->geometry.height,
- width, height);
-
- return 0;
-}
-
-static int decutf8Work(hb_work_object_t * w,
- hb_buffer_t **buf_in, hb_buffer_t **buf_out)
-{
- hb_work_private_t * pv = w->private_data;
- hb_buffer_t * in = *buf_in;
- hb_buffer_t *out = *buf_in;
-
- *buf_in = NULL;
- if (in->s.flags & HB_BUF_FLAG_EOF)
- {
- *buf_out = in;
- return HB_WORK_DONE;
- }
-
- // Warn if the subtitle's duration has not been passed through by the
- // demuxer, which will prevent the subtitle from displaying at all
- if (out->s.stop == 0)
- {
- hb_log("decutf8sub: subtitle packet lacks duration");
- }
-
- hb_srt_to_ssa(out, ++pv->line);
- out->s.frametype = HB_FRAME_SUBTITLE;
- *buf_out = out;
-
- return HB_WORK_OK;
-}
-
-static void decutf8Close(hb_work_object_t *w)
-{
- free(w->private_data);
-}
-
-hb_work_object_t hb_decutf8sub =
-{
- WORK_DECUTF8SUB,
- "UTF-8 Subtitle Decoder",
- decutf8Init,
- decutf8Work,
- decutf8Close
-};
diff --git a/libhb/handbrake/common.h b/libhb/handbrake/common.h
index 40c2e31ba..175eae8e5 100644
--- a/libhb/handbrake/common.h
+++ b/libhb/handbrake/common.h
@@ -991,6 +991,7 @@ struct hb_subtitle_s
#ifdef __LIBHB__
/* Internal data */
uint32_t codec; /* Input "codec" */
+ uint32_t codec_param; /* Per-codec config info */
uint32_t reg_desc; /* registration descriptor of source */
uint32_t stream_type; /* stream type from source stream */
uint32_t substream_type; /* substream for multiplexed streams */
@@ -1257,7 +1258,7 @@ extern hb_work_object_t hb_decsrtsub;
extern hb_work_object_t hb_decutf8sub;
extern hb_work_object_t hb_dectx3gsub;
extern hb_work_object_t hb_decssasub;
-extern hb_work_object_t hb_decpgssub;
+extern hb_work_object_t hb_decavsub;
extern hb_work_object_t hb_encavcodec;
extern hb_work_object_t hb_encqsv;
extern hb_work_object_t hb_encx264;
diff --git a/libhb/handbrake/decavsub.h b/libhb/handbrake/decavsub.h
new file mode 100644
index 000000000..9a6e77c39
--- /dev/null
+++ b/libhb/handbrake/decavsub.h
@@ -0,0 +1,22 @@
+/* decavsub.h
+
+ Copyright (c) 2003-2019 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
+ */
+
+#ifndef HANDBRAKE_DECAVSUB_H
+#define HANDBRAKE_DECAVSUB_H
+
+#include "handbrake/handbrake.h"
+
+typedef struct hb_avsub_context_s hb_avsub_context_t;
+
+hb_avsub_context_t * decavsubInit( hb_work_object_t * w, hb_job_t * job );
+int decavsubWork( hb_avsub_context_t * ctx,
+ hb_buffer_t ** in, hb_buffer_t ** out );
+void decavsubClose( hb_avsub_context_t * ctx );
+
+#endif // HANDBRAKE_DECAVSUB_H
diff --git a/libhb/handbrake/decsrtsub.h b/libhb/handbrake/decsrtsub.h
deleted file mode 100644
index 4b1283328..000000000
--- a/libhb/handbrake/decsrtsub.h
+++ /dev/null
@@ -1,16 +0,0 @@
-/* decsrtsub.h
- *
- * Copyright (c) 2003-2020 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
- */
-
-#ifndef HANDBRAKE_DECSRTSUB_H
-#define HANDBRAKE_DECSRTSUB_H
-
-void hb_srt_to_ssa(hb_buffer_t *sub_in, int line);
-
-#endif // HANDBRAKE_DECSRTSUB_H
-
diff --git a/libhb/handbrake/decssasub.h b/libhb/handbrake/decssasub.h
deleted file mode 100644
index 3364b1751..000000000
--- a/libhb/handbrake/decssasub.h
+++ /dev/null
@@ -1,13 +0,0 @@
-/* decssasub.h
- *
- * Copyright (c) 2003-2020 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
- */
-
-#ifndef HANDBRAKE_DECSSASUB_H
-#define HANDBRAKE_DECSSASUB_H
-
-#endif // HANDBRAKE_DECSSASUB_H
diff --git a/libhb/handbrake/internal.h b/libhb/handbrake/internal.h
index 0afb2970c..fe5690968 100644
--- a/libhb/handbrake/internal.h
+++ b/libhb/handbrake/internal.h
@@ -437,7 +437,6 @@ enum
WORK_DECCC608,
WORK_DECVOBSUB,
WORK_DECSRTSUB,
- WORK_DECUTF8SUB,
WORK_DECTX3GSUB,
WORK_DECSSASUB,
WORK_ENCVOBSUB,
@@ -457,7 +456,7 @@ enum
WORK_ENCAVCODEC_AUDIO,
WORK_MUX,
WORK_READER,
- WORK_DECPGSSUB
+ WORK_DECAVSUB
};
extern hb_filter_object_t hb_filter_detelecine;
diff --git a/libhb/hb.c b/libhb/hb.c
index edc5ec187..f28b7287c 100644
--- a/libhb/hb.c
+++ b/libhb/hb.c
@@ -1710,11 +1710,10 @@ int hb_global_init()
hb_register(&hb_decavcodeca);
hb_register(&hb_declpcm);
hb_register(&hb_deccc608);
- hb_register(&hb_decpgssub);
+ hb_register(&hb_decavsub);
hb_register(&hb_decsrtsub);
hb_register(&hb_decssasub);
hb_register(&hb_dectx3gsub);
- hb_register(&hb_decutf8sub);
hb_register(&hb_decvobsub);
hb_register(&hb_encvobsub);
hb_register(&hb_encavcodec);
diff --git a/libhb/muxcommon.c b/libhb/muxcommon.c
index 78efdde9f..c698551e9 100644
--- a/libhb/muxcommon.c
+++ b/libhb/muxcommon.c
@@ -7,7 +7,6 @@
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/decssasub.h"
#define MIN_BUFFERING (1024*1024*10)
#define MAX_BUFFERING (1024*1024*50)
diff --git a/libhb/stream.c b/libhb/stream.c
index f76a5b7e2..b3c304874 100644
--- a/libhb/stream.c
+++ b/libhb/stream.c
@@ -85,7 +85,7 @@ static const stream2codec_t st2codec[256] = {
st(0x8a, A, HB_ACODEC_DCA, AV_CODEC_ID_DTS, "DTS"),
- st(0x90, S, WORK_DECPGSSUB, 0, "PGS Subtitle"),
+ st(0x90, S, WORK_DECAVSUB, AV_CODEC_ID_HDMV_PGS_SUBTITLE, "PGS Subtitle"),
// 0x91 can be AC3 or BD Interactive Graphics Stream.
st(0x91, U, 0, 0, "AC3/IGS"),
st(0x92, N, 0, 0, "Subtitle"),
@@ -1976,13 +1976,24 @@ static void pes_add_subtitle_to_title(
subtitle->timebase.num = 1;
subtitle->timebase.den = 90000;
- switch ( pes->codec )
+ switch (pes->codec)
{
- case WORK_DECPGSSUB:
- subtitle->source = PGSSUB;
- subtitle->format = PICTURESUB;
- subtitle->config.dest = RENDERSUB;
- break;
+ case WORK_DECAVSUB:
+ {
+ switch (pes->codec_param)
+ {
+ case AV_CODEC_ID_HDMV_PGS_SUBTITLE:
+ subtitle->source = PGSSUB;
+ subtitle->format = PICTURESUB;
+ subtitle->config.dest = RENDERSUB;
+ break;
+ default:
+ // Unrecognized, don't add to list
+ hb_log("unrecognized subtitle!");
+ free( subtitle );
+ return;
+ }
+ } break;
case WORK_DECVOBSUB:
subtitle->source = VOBSUB;
subtitle->format = PICTURESUB;
@@ -1994,16 +2005,18 @@ static void pes_add_subtitle_to_title(
free( subtitle );
return;
}
+
lang = lang_for_code( pes->lang_code );
snprintf(subtitle->lang, sizeof( subtitle->lang ), "%s [%s]",
strlen(lang->native_name) ? lang->native_name : lang->eng_name,
hb_subsource_name(subtitle->source));
snprintf(subtitle->iso639_2, sizeof( subtitle->iso639_2 ), "%s",
lang->iso639_2);
- subtitle->reg_desc = stream->reg_desc;
- subtitle->stream_type = pes->stream_type;
+ subtitle->reg_desc = stream->reg_desc;
+ subtitle->stream_type = pes->stream_type;
subtitle->substream_type = pes->stream_id_ext;
- subtitle->codec = pes->codec;
+ subtitle->codec = pes->codec;
+ subtitle->codec_param = pes->codec_param;
// Create a default palette since vob files do not include the
// vobsub palette.
@@ -5475,10 +5488,11 @@ static void add_ffmpeg_subtitle( hb_title_t *title, hb_stream_t *stream, int id
break;
case AV_CODEC_ID_TEXT:
case AV_CODEC_ID_SUBRIP:
- subtitle->format = TEXTSUB;
- subtitle->source = UTF8SUB;
+ subtitle->format = TEXTSUB;
+ subtitle->source = UTF8SUB;
subtitle->config.dest = PASSTHRUSUB;
- subtitle->codec = WORK_DECUTF8SUB;
+ subtitle->codec = WORK_DECAVSUB;
+ subtitle->codec_param = codecpar->codec_id;
break;
case AV_CODEC_ID_MOV_TEXT: // TX3G
subtitle->format = TEXTSUB;
@@ -5487,16 +5501,18 @@ static void add_ffmpeg_subtitle( hb_title_t *title, hb_stream_t *stream, int id
subtitle->codec = WORK_DECTX3GSUB;
break;
case AV_CODEC_ID_ASS:
- subtitle->format = TEXTSUB;
- subtitle->source = SSASUB;
+ subtitle->format = TEXTSUB;
+ subtitle->source = SSASUB;
subtitle->config.dest = PASSTHRUSUB;
- subtitle->codec = WORK_DECSSASUB;
+ subtitle->codec = WORK_DECAVSUB;
+ subtitle->codec_param = codecpar->codec_id;
break;
case AV_CODEC_ID_HDMV_PGS_SUBTITLE:
- subtitle->format = PICTURESUB;
- subtitle->source = PGSSUB;
+ subtitle->format = PICTURESUB;
+ subtitle->source = PGSSUB;
subtitle->config.dest = RENDERSUB;
- subtitle->codec = WORK_DECPGSSUB;
+ subtitle->codec = WORK_DECAVSUB;
+ subtitle->codec_param = codecpar->codec_id;
break;
case AV_CODEC_ID_EIA_608:
subtitle->format = TEXTSUB;
@@ -5966,6 +5982,7 @@ hb_buffer_t * hb_ffmpeg_read( hb_stream_t *stream )
}
++stream->frames;
}
+ AVStream *s = stream->ffmpeg_ic->streams[stream->ffmpeg_pkt.stream_index];
if ( stream->ffmpeg_pkt.size <= 0 )
{
// M$ "invalid and inefficient" packed b-frames require 'null frames'
@@ -5985,8 +6002,24 @@ hb_buffer_t * hb_ffmpeg_read( hb_stream_t *stream )
av_packet_unref(&stream->ffmpeg_pkt);
return hb_ffmpeg_read( stream );
}
- buf = hb_buffer_init( stream->ffmpeg_pkt.size );
- memcpy( buf->data, stream->ffmpeg_pkt.data, stream->ffmpeg_pkt.size );
+ switch (s->codecpar->codec_type)
+ {
+ case AVMEDIA_TYPE_SUBTITLE:
+ // Some ffmpeg subtitle decoders expect a null terminated
+ // string, but the null is not included in the packet size.
+ // WTF ffmpeg.
+ buf = hb_buffer_init(stream->ffmpeg_pkt.size + 1);
+ memcpy(buf->data, stream->ffmpeg_pkt.data,
+ stream->ffmpeg_pkt.size);
+ buf->data[stream->ffmpeg_pkt.size] = 0;
+ buf->size = stream->ffmpeg_pkt.size;
+ break;
+ default:
+ buf = hb_buffer_init(stream->ffmpeg_pkt.size);
+ memcpy(buf->data, stream->ffmpeg_pkt.data,
+ stream->ffmpeg_pkt.size);
+ break;
+ }
const uint8_t *palette;
int size;
@@ -6006,7 +6039,6 @@ hb_buffer_t * hb_ffmpeg_read( hb_stream_t *stream )
// compute a conversion factor to go from the ffmpeg
// timebase for the stream to HB's 90kHz timebase.
- AVStream *s = stream->ffmpeg_ic->streams[stream->ffmpeg_pkt.stream_index];
double tsconv = (double)90000. * s->time_base.num / s->time_base.den;
int64_t offset = 90000LL * ffmpeg_initial_timestamp(stream) / AV_TIME_BASE;
diff --git a/libhb/sync.c b/libhb/sync.c
index d1f0c1970..66d4ebd2f 100644
--- a/libhb/sync.c
+++ b/libhb/sync.c
@@ -2835,6 +2835,10 @@ static hb_buffer_t * sanitizeSubtitle(
hb_buffer_list_append(&out_list, sub);
sub = hb_buffer_list_rem_head(&list);
}
+ if (sub != NULL)
+ {
+ hb_buffer_close(&sub);
+ }
return hb_buffer_list_clear(&out_list);
}