static void decode(struct sd *sd, struct demux_packet *packet) { struct sd_ass_priv *ctx = sd->priv; ASS_Track *track = ctx->ass_track; if (ctx->converter) { if (!sd->opts->sub_clear_on_seek && packet->pos >= 0 && check_packet_seen(sd, packet->pos)) return; if (packet->duration < 0) { if (!ctx->duration_unknown) { MP_WARN(sd, "Subtitle with unknown duration.\n"); ctx->duration_unknown = true; } packet->duration = UNKNOWN_DURATION; } char **r = lavc_conv_decode(ctx->converter, packet); for (int n = 0; r && r[n]; n++) ass_process_data(track, r[n], strlen(r[n])); if (ctx->duration_unknown) { for (int n = 0; n < track->n_events - 1; n++) { if (track->events[n].Duration == UNKNOWN_DURATION * 1000) { track->events[n].Duration = track->events[n + 1].Start - track->events[n].Start; } } } } else { // Note that for this packet format, libass has an internal mechanism // for discarding duplicate (already seen) packets. ass_process_chunk(track, packet->buffer, packet->len, llrint(packet->pts * 1000), llrint(packet->duration * 1000)); } }
static void libass_push_packet(csri_inst *inst, const void *packet, size_t packetlen, double pts_start, double pts_end) { ass_process_chunk(inst->ass_track, (void *)packet, packetlen, (int)(pts_start * 1000), (int)((pts_end - pts_start) * 1000)); }
static void decode(struct sd *sd, struct demux_packet *packet) { struct sd_ass_priv *ctx = sd->priv; ASS_Track *track = ctx->ass_track; long long ipts = packet->pts * 1000 + 0.5; long long iduration = packet->duration * 1000 + 0.5; if (strcmp(sd->codec, "ass") == 0) { ass_process_chunk(track, packet->buffer, packet->len, ipts, iduration); return; } else if (strcmp(sd->codec, "ssa") == 0) { // broken ffmpeg ASS packet format ctx->flush_on_seek = true; ass_process_data(track, packet->buffer, packet->len); return; } // plaintext subs if (packet->pts == MP_NOPTS_VALUE) { MP_WARN(sd, "Subtitle without pts, ignored\n"); return; } if (ctx->extend_event >= 0 && ctx->extend_event < track->n_events) { ASS_Event *event = &track->events[ctx->extend_event]; if (event->Start <= ipts) event->Duration = ipts - event->Start; ctx->extend_event = -1; } unsigned char *text = packet->buffer; if (!sd->no_remove_duplicates) { for (int i = 0; i < track->n_events; i++) { if (track->events[i].Start == ipts && (track->events[i].Duration == iduration) && strcmp(track->events[i].Text, text) == 0) return; // We've already added this subtitle } } int eid = ass_alloc_event(track); ASS_Event *event = track->events + eid; if (packet->duration == 0) { MP_WARN(sd, "Subtitle without duration or " "duration set to 0 at pts %f.\n", packet->pts); } if (packet->duration < 0) { // Assume unknown duration. The FFmpeg API is very unclear about this. MP_WARN(sd, "Assuming subtitle without duration at pts %f\n", packet->pts); // _If_ there's a next subtitle, the duration will be adjusted again. // If not, show it forever. iduration = INT_MAX; ctx->extend_event = eid; } event->Start = ipts; event->Duration = iduration; event->Style = track->default_style; event->Text = strdup(text); }
static void decode(struct sh_sub *sh, struct osd_state *osd, void *data, int data_len, double pts, double duration) { unsigned char *text = data; struct sd_ass_priv *ctx = sh->context; ASS_Track *track = ctx->ass_track; if (sh->type == 'a') { // ssa/ass subs ass_process_chunk(track, data, data_len, (long long)(pts*1000 + 0.5), (long long)(duration*1000 + 0.5)); return; } // plaintext subs if (pts == MP_NOPTS_VALUE) { mp_msg(MSGT_SUBREADER, MSGL_WARN, "Subtitle without pts, ignored\n"); return; } long long ipts = pts * 1000 + 0.5; long long iduration = duration * 1000 + 0.5; if (ctx->incomplete_event) { ctx->incomplete_event = false; ASS_Event *event = track->events + track->n_events - 1; if (ipts <= event->Start) free_last_event(track); else event->Duration = ipts - event->Start; } // Note: we rely on there being guaranteed 0 bytes after data packets int len = strlen(text); if (len < 5) { // Some tracks use a whitespace (but not empty) packet to mark end // of previous subtitle. for (int i = 0; i < len; i++) if (!strchr(" \f\n\r\t\v", text[i])) goto not_all_whitespace; return; } not_all_whitespace:; char buf[500]; subassconvert_subrip(text, buf, sizeof(buf)); for (int i = 0; i < track->n_events; i++) if (track->events[i].Start == ipts && (duration <= 0 || track->events[i].Duration == iduration) && strcmp(track->events[i].Text, buf) == 0) return; // We've already added this subtitle if (duration <= 0) { iduration = 10000; ctx->incomplete_event = true; } int eid = ass_alloc_event(track); ASS_Event *event = track->events + eid; event->Start = ipts; event->Duration = iduration; event->Style = track->default_style; event->Text = strdup(buf); }
bool CDVDSubtitlesLibass::DecodeDemuxPkt(const char* data, int size, double start, double duration) { CSingleLock lock(m_section); if(!m_track) { CLog::Log(LOGERROR, "CDVDSubtitlesLibass: No SSA header found."); return false; } //! @bug libass isn't const correct ass_process_chunk(m_track, const_cast<char*>(data), size, DVD_TIME_TO_MSEC(start), DVD_TIME_TO_MSEC(duration)); return true; }
static void decode(struct sd *sd, struct demux_packet *packet) { struct sd_ass_priv *ctx = sd->priv; ASS_Track *track = ctx->ass_track; if (ctx->converter) { if (!sd->opts->sub_clear_on_seek && check_packet_seen(sd, packet->pos)) return; char **r = lavc_conv_decode(ctx->converter, packet); for (int n = 0; r && r[n]; n++) ass_process_data(track, r[n], strlen(r[n])); } else { // Note that for this packet format, libass has an internal mechanism // for discarding duplicate (already seen) packets. ass_process_chunk(track, packet->buffer, packet->len, lrint(packet->pts * 1000), lrint(packet->duration * 1000)); } }
static void decode(struct sd *sd, struct demux_packet *packet) { struct sd_ass_priv *ctx = sd->priv; ASS_Track *track = ctx->ass_track; long long ipts = packet->pts * 1000 + 0.5; long long iduration = packet->duration * 1000 + 0.5; if (strcmp(sd->codec, "ass") == 0) { ass_process_chunk(track, packet->buffer, packet->len, ipts, iduration); return; } else if (strcmp(sd->codec, "ssa") == 0) { // broken ffmpeg ASS packet format ctx->flush_on_seek = true; ass_process_data(track, packet->buffer, packet->len); return; } // plaintext subs if (packet->pts == MP_NOPTS_VALUE) { mp_msg(MSGT_SUBREADER, MSGL_WARN, "Subtitle without pts, ignored\n"); return; } if (packet->duration <= 0) { mp_msg(MSGT_SUBREADER, MSGL_WARN, "Subtitle without duration or " "duration set to 0 at pts %f, ignored\n", packet->pts); return; } unsigned char *text = packet->buffer; if (!sd->no_remove_duplicates) { for (int i = 0; i < track->n_events; i++) { if (track->events[i].Start == ipts && (track->events[i].Duration == iduration) && strcmp(track->events[i].Text, text) == 0) return; // We've already added this subtitle } } int eid = ass_alloc_event(track); ASS_Event *event = track->events + eid; event->Start = ipts; event->Duration = iduration; event->Style = track->default_style; event->Text = strdup(text); }
/**************************************************************************** * DecodeBlock: ****************************************************************************/ static subpicture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block ) { decoder_sys_t *p_sys = p_dec->p_sys; subpicture_t *p_spu = NULL; block_t *p_block; if( !pp_block || *pp_block == NULL ) return NULL; p_block = *pp_block; *pp_block = NULL; if( p_block->i_flags & BLOCK_FLAG_CORRUPTED ) { Flush( p_dec ); block_Release( p_block ); return NULL; } if( p_block->i_buffer == 0 || p_block->p_buffer[0] == '\0' ) { block_Release( p_block ); return NULL; } subpicture_updater_sys_t *p_spu_sys = malloc( sizeof(*p_spu_sys) ); if( !p_spu_sys ) { block_Release( p_block ); return NULL; } subpicture_updater_t updater = { .pf_validate = SubpictureValidate, .pf_update = SubpictureUpdate, .pf_destroy = SubpictureDestroy, .p_sys = p_spu_sys, }; p_spu = decoder_NewSubpicture( p_dec, &updater ); if( !p_spu ) { msg_Warn( p_dec, "can't get spu buffer" ); free( p_spu_sys ); block_Release( p_block ); return NULL; } p_spu_sys->p_img = NULL; p_spu_sys->p_dec_sys = p_sys; p_spu_sys->i_subs_len = p_block->i_buffer; p_spu_sys->p_subs_data = malloc( p_block->i_buffer ); p_spu_sys->i_pts = p_block->i_pts; if( !p_spu_sys->p_subs_data ) { subpicture_Delete( p_spu ); block_Release( p_block ); return NULL; } memcpy( p_spu_sys->p_subs_data, p_block->p_buffer, p_block->i_buffer ); p_spu->i_start = p_block->i_pts; p_spu->i_stop = __MAX( p_sys->i_max_stop, p_block->i_pts + p_block->i_length ); p_spu->b_ephemer = true; p_spu->b_absolute = true; p_sys->i_max_stop = p_spu->i_stop; vlc_mutex_lock( &p_sys->lock ); if( p_sys->p_track ) { ass_process_chunk( p_sys->p_track, p_spu_sys->p_subs_data, p_spu_sys->i_subs_len, p_block->i_pts / 1000, p_block->i_length / 1000 ); } vlc_mutex_unlock( &p_sys->lock ); DecSysHold( p_sys ); /* Keep a reference for the returned subpicture */ block_Release( p_block ); return p_spu; }
void update_subtitles(sh_video_t *sh_video, double refpts, demux_stream_t *d_dvdsub, int reset) { double curpts = refpts - sub_delay; unsigned char *packet=NULL; int len; int type = d_dvdsub->sh ? ((sh_sub_t *)d_dvdsub->sh)->type : 'v'; static subtitle subs; if (reset) { sub_clear_text(&subs, MP_NOPTS_VALUE); if (vo_sub) { set_osd_subtitle(NULL); } if (vo_spudec) { spudec_reset(vo_spudec); vo_osd_changed(OSDTYPE_SPU); } #ifdef CONFIG_FFMPEG if (is_av_sub(type)) reset_avsub(d_dvdsub->sh); #endif } // find sub if (subdata) { if (sub_fps==0) sub_fps = sh_video ? sh_video->fps : 25; current_module = "find_sub"; if (refpts > sub_last_pts || refpts < sub_last_pts-1.0) { find_sub(subdata, curpts * (subdata->sub_uses_time ? 100. : sub_fps)); if (vo_sub) vo_sub_last = vo_sub; // FIXME! frame counter... sub_last_pts = refpts; } } // DVD sub: if (vo_config_count && (vobsub_id >= 0 || type == 'v')) { int timestamp; current_module = "spudec"; /* Get a sub packet from the DVD or a vobsub */ while(1) { // Vobsub len = 0; if (vo_vobsub) { if (curpts >= 0) { len = vobsub_get_packet(vo_vobsub, curpts, (void**)&packet, ×tamp); if (len > 0) { mp_dbg(MSGT_CPLAYER,MSGL_V,"\rVOB sub: len=%d v_pts=%5.3f v_timer=%5.3f sub=%5.3f ts=%d \n",len,refpts,sh_video->timer,timestamp / 90000.0,timestamp); } } } else { // DVD sub len = ds_get_packet_sub(d_dvdsub, (unsigned char**)&packet, NULL, NULL); if (len > 0) { // XXX This is wrong, sh_video->pts can be arbitrarily // much behind demuxing position. Unfortunately using // d_video->pts which would have been the simplest // improvement doesn't work because mpeg specific hacks // in video.c set d_video->pts to 0. float x = d_dvdsub->pts - refpts; if (x > -20 && x < 20) // prevent missing subs on pts reset timestamp = 90000*d_dvdsub->pts; else timestamp = 90000*curpts; mp_dbg(MSGT_CPLAYER, MSGL_V, "\rDVD sub: len=%d " "v_pts=%5.3f s_pts=%5.3f ts=%d \n", len, refpts, d_dvdsub->pts, timestamp); } } if (len<=0 || !packet) break; // create it only here, since with some broken demuxers we might // type = v but no DVD sub and we currently do not change the // "original frame size" ever after init, leading to wrong-sized // PGS subtitles. if (!vo_spudec) vo_spudec = spudec_new(NULL); if (vo_vobsub || timestamp >= 0) spudec_assemble(vo_spudec, packet, len, timestamp); } } else if (is_text_sub(type) || is_av_sub(type) || type == 'd') { int orig_type = type; double endpts; if (type == 'd' && !d_dvdsub->demuxer->teletext) { tt_stream_props tsp = {0}; void *ptr = &tsp; if (teletext_control(NULL, TV_VBI_CONTROL_START, &ptr) == VBI_CONTROL_TRUE) d_dvdsub->demuxer->teletext = ptr; } if (d_dvdsub->non_interleaved) ds_get_next_pts(d_dvdsub); while (1) { double subpts = curpts; type = orig_type; len = ds_get_packet_sub(d_dvdsub, &packet, &subpts, &endpts); if (len < 0) break; if (is_av_sub(type)) { #ifdef CONFIG_FFMPEG type = decode_avsub(d_dvdsub->sh, &packet, &len, &subpts, &endpts); if (type <= 0) #endif continue; } if (type == 'm') { if (len < 2) continue; len = FFMIN(len - 2, AV_RB16(packet)); packet += 2; } if (type == 'd') { if (d_dvdsub->demuxer->teletext) { uint8_t *p = packet; p++; len--; while (len >= 46) { int sublen = p[1]; if (p[0] == 2 || p[0] == 3) teletext_control(d_dvdsub->demuxer->teletext, TV_VBI_CONTROL_DECODE_DVB, p + 2); p += sublen + 2; len -= sublen + 2; } } continue; } #ifdef CONFIG_ASS if (ass_enabled) { sh_sub_t* sh = d_dvdsub->sh; ass_track = sh ? sh->ass_track : NULL; if (!ass_track) continue; if (type == 'a') { // ssa/ass subs with libass if (len > 10 && memcmp(packet, "Dialogue: ", 10) == 0) ass_process_data(ass_track, packet, len); else ass_process_chunk(ass_track, packet, len, (long long)(subpts*1000 + 0.5), (long long)((endpts-subpts)*1000 + 0.5)); } else { // plaintext subs with libass if (subpts != MP_NOPTS_VALUE) { subtitle tmp_subs = {0}; if (endpts == MP_NOPTS_VALUE) endpts = subpts + 3; sub_add_text(&tmp_subs, packet, len, endpts, 0); tmp_subs.start = subpts * 100; tmp_subs.end = endpts * 100; ass_process_subtitle(ass_track, &tmp_subs); sub_clear_text(&tmp_subs, MP_NOPTS_VALUE); } } continue; } #endif if (subpts != MP_NOPTS_VALUE) { if (endpts == MP_NOPTS_VALUE) sub_clear_text(&subs, MP_NOPTS_VALUE); if (type == 'a') { // ssa/ass subs without libass => convert to plaintext int i; unsigned char* p = packet; int skip_commas = 8; if (len > 10 && memcmp(packet, "Dialogue: ", 10) == 0) skip_commas = 9; for (i=0; i < skip_commas && *p != '\0'; p++) if (*p == ',') i++; if (*p == '\0') /* Broken line? */ continue; len -= p - packet; packet = p; } sub_add_text(&subs, packet, len, endpts, 1); set_osd_subtitle(&subs); } if (d_dvdsub->non_interleaved) ds_get_next_pts(d_dvdsub); } if (sub_clear_text(&subs, curpts)) set_osd_subtitle(&subs); } if (vo_spudec) { spudec_heartbeat(vo_spudec, 90000*curpts); if (spudec_changed(vo_spudec)) vo_osd_changed(OSDTYPE_SPU); } current_module=NULL; }
static av_cold int init_subtitles(AVFilterContext *ctx) { int j, ret, sid; int k = 0; AVDictionary *codec_opts = NULL; AVFormatContext *fmt = NULL; AVCodecContext *dec_ctx = NULL; AVCodec *dec = NULL; const AVCodecDescriptor *dec_desc; AVStream *st; AVPacket pkt; AssContext *ass = ctx->priv; /* Init libass */ ret = init(ctx); if (ret < 0) return ret; ass->track = ass_new_track(ass->library); if (!ass->track) { av_log(ctx, AV_LOG_ERROR, "Could not create a libass track\n"); return AVERROR(EINVAL); } /* Open subtitles file */ ret = avformat_open_input(&fmt, ass->filename, NULL, NULL); if (ret < 0) { av_log(ctx, AV_LOG_ERROR, "Unable to open %s\n", ass->filename); goto end; } ret = avformat_find_stream_info(fmt, NULL); if (ret < 0) goto end; /* Locate subtitles stream */ if (ass->stream_index < 0) ret = av_find_best_stream(fmt, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0); else { ret = -1; if (ass->stream_index < fmt->nb_streams) { for (j = 0; j < fmt->nb_streams; j++) { if (fmt->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { if (ass->stream_index == k) { ret = j; break; } k++; } } } } if (ret < 0) { av_log(ctx, AV_LOG_ERROR, "Unable to locate subtitle stream in %s\n", ass->filename); goto end; } sid = ret; st = fmt->streams[sid]; /* Load attached fonts */ for (j = 0; j < fmt->nb_streams; j++) { AVStream *st = fmt->streams[j]; if (st->codecpar->codec_type == AVMEDIA_TYPE_ATTACHMENT && attachment_is_font(st)) { const AVDictionaryEntry *tag = NULL; tag = av_dict_get(st->metadata, "filename", NULL, AV_DICT_MATCH_CASE); if (tag) { av_log(ctx, AV_LOG_DEBUG, "Loading attached font: %s\n", tag->value); ass_add_font(ass->library, tag->value, st->codecpar->extradata, st->codecpar->extradata_size); } else { av_log(ctx, AV_LOG_WARNING, "Font attachment has no filename, ignored.\n"); } } } /* Initialize fonts */ ass_set_fonts(ass->renderer, NULL, NULL, 1, NULL, 1); /* Open decoder */ dec = avcodec_find_decoder(st->codecpar->codec_id); if (!dec) { av_log(ctx, AV_LOG_ERROR, "Failed to find subtitle codec %s\n", avcodec_get_name(st->codecpar->codec_id)); return AVERROR(EINVAL); } dec_desc = avcodec_descriptor_get(st->codecpar->codec_id); if (dec_desc && !(dec_desc->props & AV_CODEC_PROP_TEXT_SUB)) { av_log(ctx, AV_LOG_ERROR, "Only text based subtitles are currently supported\n"); return AVERROR_PATCHWELCOME; } if (ass->charenc) av_dict_set(&codec_opts, "sub_charenc", ass->charenc, 0); if (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57,26,100)) av_dict_set(&codec_opts, "sub_text_format", "ass", 0); dec_ctx = avcodec_alloc_context3(dec); if (!dec_ctx) return AVERROR(ENOMEM); ret = avcodec_parameters_to_context(dec_ctx, st->codecpar); if (ret < 0) goto end; /* * This is required by the decoding process in order to rescale the * timestamps: in the current API the decoded subtitles have their pts * expressed in AV_TIME_BASE, and thus the lavc internals need to know the * stream time base in order to achieve the rescaling. * * That API is old and needs to be reworked to match behaviour with A/V. */ av_codec_set_pkt_timebase(dec_ctx, st->time_base); ret = avcodec_open2(dec_ctx, NULL, &codec_opts); if (ret < 0) goto end; if (ass->force_style) { char **list = NULL; char *temp = NULL; char *ptr = av_strtok(ass->force_style, ",", &temp); int i = 0; while (ptr) { av_dynarray_add(&list, &i, ptr); if (!list) { ret = AVERROR(ENOMEM); goto end; } ptr = av_strtok(NULL, ",", &temp); } av_dynarray_add(&list, &i, NULL); if (!list) { ret = AVERROR(ENOMEM); goto end; } ass_set_style_overrides(ass->library, list); av_free(list); } /* Decode subtitles and push them into the renderer (libass) */ if (dec_ctx->subtitle_header) ass_process_codec_private(ass->track, dec_ctx->subtitle_header, dec_ctx->subtitle_header_size); av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; while (av_read_frame(fmt, &pkt) >= 0) { int i, got_subtitle; AVSubtitle sub = {0}; if (pkt.stream_index == sid) { ret = avcodec_decode_subtitle2(dec_ctx, &sub, &got_subtitle, &pkt); if (ret < 0) { av_log(ctx, AV_LOG_WARNING, "Error decoding: %s (ignored)\n", av_err2str(ret)); } else if (got_subtitle) { const int64_t start_time = av_rescale_q(sub.pts, AV_TIME_BASE_Q, av_make_q(1, 1000)); const int64_t duration = sub.end_display_time; for (i = 0; i < sub.num_rects; i++) { char *ass_line = sub.rects[i]->ass; if (!ass_line) break; if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,25,100)) ass_process_data(ass->track, ass_line, strlen(ass_line)); else ass_process_chunk(ass->track, ass_line, strlen(ass_line), start_time, duration); } } } av_packet_unref(&pkt); avsubtitle_free(&sub); } end: av_dict_free(&codec_opts); avcodec_close(dec_ctx); avcodec_free_context(&dec_ctx); avformat_close_input(&fmt); return ret; }