/* decssasub.c Copyright (c) 2003-2021 HandBrake Team This file is part of the HandBrake source code Homepage: . 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 */ /* * Converts SSA subtitles to either: * (1) TEXTSUB format: UTF-8 subtitles with limited HTML-style markup (, , ), or * (2) PICTURESUB format, using libass. * * SSA format references: * http://www.matroska.org/technical/specs/subtitles/ssa.html * http://moodub.free.fr/video/ass-specs.doc * vlc-1.0.4/modules/codec/subtitles/subsass.c:ParseSSAString * * libass references: * libass-0.9.9/ass.h * vlc-1.0.4/modules/codec/libass.c * * @author David Foster (davidfstr) */ #include "handbrake/handbrake.h" #include "handbrake/decavsub.h" #include "libavformat/avformat.h" struct hb_work_private_s { AVFormatContext * ic; hb_avsub_context_t * ctx; AVPacket avpkt; hb_job_t * job; hb_subtitle_t * subtitle; // Time of first desired subtitle adjusted by reader_pts_offset uint64_t start_time; uint64_t stop_time; }; static int extradataInit( hb_work_private_t * pv ) { if (pv->ic->nb_streams < 1) { hb_error("SSA demux found no streams"); return 1; } AVStream * st = pv->ic->streams[0]; if (st->codecpar->codec_id != AV_CODEC_ID_ASS) { hb_error("SSA demux found wrong codec_id %x", st->codecpar->codec_id); return 1; } if (st->codecpar->extradata != NULL) { pv->subtitle->extradata = malloc(st->codecpar->extradata_size + 1); memcpy(pv->subtitle->extradata, st->codecpar->extradata, st->codecpar->extradata_size); pv->subtitle->extradata[st->codecpar->extradata_size] = 0; pv->subtitle->extradata_size = st->codecpar->extradata_size + 1; } return 0; } static int decssaInit( hb_work_object_t * w, hb_job_t * job ) { hb_work_private_t * pv = NULL; int ii; if (w->subtitle->config.src_filename == NULL) { hb_error("No SSA subtitle file specified"); goto fail; } pv = calloc( 1, sizeof( hb_work_private_t ) ); if (pv == NULL) { goto fail; } w->private_data = pv; pv->job = job; pv->subtitle = w->subtitle; av_init_packet(&pv->avpkt); if (avformat_open_input(&pv->ic, pv->subtitle->config.src_filename, NULL, NULL ) < 0 ) { hb_error("Could not open the SSA subtitle file '%s'\n", pv->subtitle->config.src_filename); goto fail; } pv->ctx = decavsubInit(w, job); if (pv->ctx == NULL) { goto fail; } if (extradataInit(pv)) { goto fail; } /* * Figure out the start and stop times from the chapters being * encoded - drop subtitle not in this range. */ pv->start_time = 0; for (ii = 1; ii < job->chapter_start; ++ii) { hb_chapter_t * chapter = hb_list_item(job->list_chapter, ii - 1); if (chapter) { pv->start_time += chapter->duration; } else { hb_error("Could not locate chapter %d for SSA start time", ii); } } pv->stop_time = pv->start_time; for (ii = job->chapter_start; ii <= job->chapter_end; ++ii) { hb_chapter_t * chapter = hb_list_item(job->list_chapter, ii - 1); if (chapter) { pv->stop_time += chapter->duration; } else { hb_error("Could not locate chapter %d for SSA start time", ii); } } hb_deep_log(3, "SSA Start time %"PRId64", stop time %"PRId64, pv->start_time, pv->stop_time); if (job->pts_to_start != 0) { // Compute start_time after reader sets reader_pts_offset pv->start_time = AV_NOPTS_VALUE; } return 0; fail: if (pv != NULL) { av_packet_unref(&pv->avpkt); decavsubClose(pv->ctx); if (pv->ic) { avformat_close_input(&pv->ic); } free(pv); w->private_data = NULL; } return 1; } static hb_buffer_t * ssa_read( hb_work_private_t * pv ) { int err; hb_buffer_t * out; 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 SSA. 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; } } if ((err = av_read_frame(pv->ic, &pv->avpkt)) < 0) { if (err != AVERROR_EOF) { hb_error("SSA demux read error %d", err); } return hb_buffer_eof_init(); } AVStream * st = pv->ic->streams[pv->avpkt.stream_index]; out = hb_buffer_init(pv->avpkt.size + 1); memcpy(out->data, pv->avpkt.data, pv->avpkt.size); out->data[pv->avpkt.size] = 0; out->size = pv->avpkt.size; double tsconv = (double)90000. * st->time_base.num / st->time_base.den; if (pv->avpkt.pts != AV_NOPTS_VALUE) { out->s.start = pv->avpkt.pts * tsconv + pv->subtitle->config.offset * 90; } if (pv->avpkt.dts != AV_NOPTS_VALUE) { out->s.renderOffset = pv->avpkt.dts * tsconv + pv->subtitle->config.offset * 90; } if (out->s.renderOffset >= 0 && out->s.start == AV_NOPTS_VALUE) { out->s.start = out->s.renderOffset; } else if (out->s.renderOffset == AV_NOPTS_VALUE && out->s.start >= 0) { out->s.renderOffset = out->s.start; } int64_t pkt_duration = pv->avpkt.duration; if (pkt_duration != AV_NOPTS_VALUE) { out->s.duration = pkt_duration * tsconv; out->s.stop = out->s.start + out->s.duration; } else { out->s.duration = (int64_t)AV_NOPTS_VALUE; } out->s.type = SUBTITLE_BUF; av_packet_unref(&pv->avpkt); if (out->s.stop <= pv->start_time || out->s.start >= pv->stop_time) { // Drop subtitles that end before the PtoP start time // or start after the PtoP stop time hb_deep_log(3, "Discarding SSA at time start %"PRId64", stop %"PRId64, out->s.start, out->s.stop); hb_buffer_close(&out); } else { // Adjust start/stop for subtitles that span PtoP start/stop if (out->s.start < pv->start_time) { out->s.start = pv->start_time; } if (out->s.stop > pv->stop_time) { out->s.stop = pv->stop_time; } out->s.start -= pv->start_time; out->s.stop -= pv->start_time; } return out; } 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; int result; in = ssa_read(pv); if (in == NULL) { return HB_WORK_OK; } result = decavsubWork(pv->ctx, &in, buf_out); if (in != NULL) { hb_buffer_close(&in); } return result; } static void decssaClose( hb_work_object_t * w ) { hb_work_private_t * pv = w->private_data; if (pv != NULL) { decavsubClose(pv->ctx); av_packet_unref(&pv->avpkt); avformat_close_input(&pv->ic); free(pv); } w->private_data = NULL; } hb_work_object_t hb_decssasub = { WORK_DECSSASUB, "SSA Subtitle Decoder", decssaInit, decssaWork, decssaClose };