diff options
author | John Stebbins <[email protected]> | 2019-12-08 15:28:07 -0800 |
---|---|---|
committer | John Stebbins <[email protected]> | 2020-03-29 08:23:16 -0600 |
commit | 9f7c5112a0381e315738c856f2344e2a64be183b (patch) | |
tree | faf1fb6fe664da48a2dcff11a535efad70efeb64 | |
parent | 7b0b2321c65362b785e149ce8c59a152139b2d15 (diff) |
decavsub: add general purpose avcodec subtitle decoder
Currently using it for pgs, srt, and ssa subtitles.
-rw-r--r-- | libhb/bd.c | 6 | ||||
-rw-r--r-- | libhb/common.c | 14 | ||||
-rw-r--r-- | libhb/decavsub.c | 650 | ||||
-rw-r--r-- | libhb/decpgssub.c | 509 | ||||
-rw-r--r-- | libhb/decsrtsub.c | 211 | ||||
-rw-r--r-- | libhb/decssasub.c | 91 | ||||
-rw-r--r-- | libhb/decutf8sub.c | 87 | ||||
-rw-r--r-- | libhb/handbrake/common.h | 3 | ||||
-rw-r--r-- | libhb/handbrake/decavsub.h | 22 | ||||
-rw-r--r-- | libhb/handbrake/decsrtsub.h | 16 | ||||
-rw-r--r-- | libhb/handbrake/decssasub.h | 13 | ||||
-rw-r--r-- | libhb/handbrake/internal.h | 3 | ||||
-rw-r--r-- | libhb/hb.c | 3 | ||||
-rw-r--r-- | libhb/muxcommon.c | 1 | ||||
-rw-r--r-- | libhb/stream.c | 76 | ||||
-rw-r--r-- | libhb/sync.c | 4 |
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); } |