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 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 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); }
void LibASS::initASS(const QByteArray &ass_data) { if (ass_sub_track && ass_sub_renderer) return; ass_sub_track = ass_new_track(ass); if (!ass_data.isEmpty()) { ass_process_data(ass_sub_track, (char *)ass_data.data(), ass_data.size()); hasASSData = true; setASSStyle(); } else { ass_alloc_style(ass_sub_track); ass_sub_track->styles[0].ScaleX = ass_sub_track->styles[0].ScaleY = 1; overridePlayRes = true; hasASSData = false; setASSStyle(); } ass_sub_renderer = ass_renderer_init(ass); ass_set_fonts(ass_sub_renderer, NULL, NULL, true, NULL, true); }
static void *ass_reader_thread(void *) { set_threadname("ass_reader_thread"); while (!sem_wait(&ass_sem)) { if (!ass_reader_running) break; ass_data *a = (ass_data *) ass_queue.pop(); if (!a) { if (!ass_reader_running) break; continue; } OpenThreads::ScopedLock<OpenThreads::Mutex> m_lock(ass_mutex); std::map<int,ASS_Track*>::iterator it = ass_map.find(a->pid); ASS_Track *track; if (it == ass_map.end()) { CFrameBuffer *fb = CFrameBuffer::getInstance(); int xres = fb->getScreenWidth(true); int yres = fb->getScreenHeight(true); if (!ass_library) { ass_library = ass_library_init(); ass_set_extract_fonts(ass_library, 1); ass_set_style_overrides(ass_library, NULL); ass_renderer = ass_renderer_init(ass_library); ass_set_frame_size(ass_renderer, xres, yres); ass_set_margins(ass_renderer, 3 * yres / 100, 3 * yres / 100, 3 * xres / 100, 3 * xres / 100); ass_set_use_margins(ass_renderer, 1); ass_set_hinting(ass_renderer, ASS_HINTING_LIGHT); ass_set_aspect_ratio(ass_renderer, 1.0, 1.0); ass_font = *sub_font_file; ass_set_fonts(ass_renderer, ass_font.c_str(), "Arial", 0, NULL, 1); } track = ass_new_track(ass_library); track->PlayResX = xres; track->PlayResY = yres; ass_size = sub_font_size; ass_set_font_scale(ass_renderer, ((double) ass_size)/ASS_CUSTOM_FONT_SIZE); if (a->c->subtitle_header) { std::string ass_hdr = ass_subtitle_header_default(); if (ass_hdr.compare((char*) a->c->subtitle_header)) { ass_process_codec_private(track, (char *) a->c->subtitle_header, a->c->subtitle_header_size); } else { // This is the FFMPEG default ASS header. Use something more suitable instead: ass_hdr = ass_subtitle_header_custom(); ass_process_codec_private(track, (char *) ass_hdr.c_str(), ass_hdr.length()); } } ass_map[a->pid] = track; if (a->pid == dvbsub_pid) ass_track = track; //fprintf(stderr, "### got subtitle track %d, subtitle header: \n---\n%s\n---\n", pid, c->subtitle_header); } else track = it->second; for (unsigned int i = 0; i < a->sub.num_rects; i++) if (a->sub.rects[i]->ass) ass_process_data(track, a->sub.rects[i]->ass, strlen(a->sub.rects[i]->ass)); avsubtitle_free(&a->sub); delete a; } ass_reader_running = false; pthread_exit(NULL); }
void LibASS::addASSEvent(const QByteArray &event) { if (!ass_sub_track || !ass_sub_renderer || event.isEmpty()) return; ass_process_data(ass_sub_track, (char *)event.data(), event.size()); }
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; }
static void VS_CC assInit(VSMap *in, VSMap *out, void **instanceData, VSNode *node, VSCore *core, const VSAPI *vsapi) { AssData *d = (AssData *) * instanceData; vsapi->setVideoInfo(d->vi, 2, node); d->ass_library = ass_library_init(); if(!d->ass_library) { vsapi->setError(out, "failed to initialize ASS library"); return; } ass_set_message_cb(d->ass_library, assDebugCallback, (void *)d->debuglevel); ass_set_extract_fonts(d->ass_library, 0); ass_set_style_overrides(d->ass_library, 0); d->ass_renderer = ass_renderer_init(d->ass_library); if(!d->ass_renderer) { vsapi->setError(out, "failed to initialize ASS renderer"); return; } ass_set_font_scale(d->ass_renderer, d->scale); ass_set_frame_size(d->ass_renderer, d->vi[0].width, d->vi[0].height); ass_set_margins(d->ass_renderer, d->margins[0], d->margins[1], d->margins[2], d->margins[3]); ass_set_use_margins(d->ass_renderer, 1); if(d->linespacing) ass_set_line_spacing(d->ass_renderer, d->linespacing); if(d->sar) { ass_set_aspect_ratio(d->ass_renderer, (double)d->vi[0].width / d->vi[0].height * d->sar, 1); } if(d->fontdir) ass_set_fonts_dir(d->ass_library, d->fontdir); ass_set_fonts(d->ass_renderer, NULL, NULL, 1, NULL, 1); if(d->file == NULL) { char *str, *text, x[16], y[16]; size_t siz; const char *fmt = "[Script Info]\n" "ScriptType: v4.00+\n" "PlayResX: %s\n" "PlayResY: %s\n" "[V4+ Styles]\n" "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n" "Style: Default,%s\n" "[Events]\n" "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n" "Dialogue: 0,0:00:00.00,0:00:10.00,Default,,0,0,0,,%s\n"; sprintf(x, "%d", d->vi[0].width); sprintf(y, "%d", d->vi[0].height); text = strrepl(d->text, "\n", "\\N"); siz = (strlen(fmt) + strlen(x) + strlen(y) + strlen(d->style) + strlen(text)) * sizeof(char); str = malloc(siz); sprintf(str, fmt, x, y, d->style, text); free(text); d->ass = ass_new_track(d->ass_library); ass_process_data(d->ass, str, strlen(str)); free(str); } else { d->ass = ass_read_file(d->ass_library, (char *)d->file, (char *)d->charset); } if(!d->ass) { vsapi->setError(out, "unable to parse input file"); return; } }