From 6debc831d34cad98d1b251920fbdb48f74a880df Mon Sep 17 00:00:00 2001 From: 0p1pp1 <0p1pp1@fms.freenet> Date: Sun, 27 Oct 2013 16:07:12 +0900 Subject: [PATCH] add support for ISDB subtitle decoding --- Makefile | 1 + av_helpers.c | 3 + command.c | 113 +- libmpdemux/demux_ts.c | 262 ++++- libmpdemux/demuxer.c | 30 +- libmpdemux/demuxer.h | 1 + libmpdemux/stheader.h | 4 + mp_msg.c | 1 + mp_msg.h | 2 + mpcommon.c | 52 +- mplayer.c | 14 +- sub/ass_mp.c | 3 +- sub/isdbsubdec.c | 2315 +++++++++++++++++++++++++++++++++++++++++ sub/isdbsubdec.h | 30 + sub/subreader.c | 8 + 15 files changed, 2805 insertions(+), 34 deletions(-) create mode 100644 sub/isdbsubdec.c create mode 100644 sub/isdbsubdec.h diff --git a/Makefile b/Makefile index 4e031209c..ac3c59af6 100644 --- a/Makefile +++ b/Makefile @@ -459,6 +459,7 @@ SRCS_COMMON = asxparser.c \ stream/url.c \ sub/eosd.c \ sub/find_sub.c \ + sub/isdbsubdec.c \ sub/osd.c \ sub/spudec.c \ sub/sub.c \ diff --git a/av_helpers.c b/av_helpers.c index e44be092b..e4eb43055 100644 --- a/av_helpers.c +++ b/av_helpers.c @@ -55,6 +55,9 @@ static void mp_msp_av_log_callback(void *ptr, int level, const char *fmt, }else if(s->codec->type == AVMEDIA_TYPE_VIDEO){ if(s->codec->decode) type= MSGT_DECVIDEO; + }else if(s->codec->type == AVMEDIA_TYPE_SUBTITLE){ + if(s->codec->decode) + type= MSGT_SUBREADER; } //FIXME subtitles, encoders (what msgt for them? there is no appropriate ...) } diff --git a/command.c b/command.c index a366759f9..a354e538d 100644 --- a/command.c +++ b/command.c @@ -1106,6 +1106,8 @@ static int mp_property_program(m_option_t *prop, int action, void *arg, } mp_property_do("switch_audio", M_PROPERTY_SET, &prog.aid, mpctx); mp_property_do("switch_video", M_PROPERTY_SET, &prog.vid, mpctx); + if (sub_source(mpctx) == SUB_SOURCE_DEMUX) + mp_property_do("sub_demux", M_PROPERTY_SET, &prog.sid, mpctx); return M_PROPERTY_OK; default: @@ -1519,6 +1521,7 @@ static int mp_property_sub(m_option_t *prop, int action, void *arg, int source_pos = -1; double pts = 0; char *sub_name; + int lang_tag = -1; update_global_sub_size(mpctx); global_sub_size = mpctx->global_sub_size; @@ -1562,13 +1565,15 @@ static int mp_property_sub(m_option_t *prop, int action, void *arg, } if (dvdsub_id >= 0) { char lang[40] = MSGTR_Unknown; + uint16_t pid; int id = dvdsub_id; // HACK: for DVDs sub->sh/id will be invalid until // we actually get the first packet if (d_sub && d_sub->sh) id = d_sub->id; demuxer_sub_lang(mpctx->demuxer, id, lang, sizeof(lang)); - snprintf(*(char **) arg, 63, "(%d) %s", dvdsub_id, lang); + pid = ((sh_sub_t *)mpctx->demuxer->s_streams[id])->sid; + snprintf(*(char **) arg, 63, "0x%04hx(%d) %s", pid, dvdsub_id, lang); return M_PROPERTY_OK; } snprintf(*(char **) arg, 63, MSGTR_Disabled); @@ -1584,14 +1589,46 @@ static int mp_property_sub(m_option_t *prop, int action, void *arg, mpctx->global_sub_pos = *(int *) arg; break; case M_PROPERTY_STEP_UP: + if (sub_source(mpctx) == SUB_SOURCE_DEMUX || + mpctx->global_sub_pos + 1 == sub_pos_by_source(mpctx, SUB_SOURCE_DEMUX)) { + int index = -1; + // try firstly to select only the PES's in the same program + if (demux_control(mpctx->demuxer, DEMUXER_CTRL_SWITCH_SUB, &index) + == DEMUXER_CTRL_OK) { + if (index >= 0) { + // found a good PES. + // sh->cur_lang_tag should be already set in _SWITCH_SUB, + // so leave lang_tag untouched (-1). + mpctx->global_sub_pos = index + + sub_pos_by_source(mpctx, SUB_SOURCE_DEMUX); + break; + } + // no good PES. + // try the next source. + mpctx->global_sub_pos = + sub_pos_by_source(mpctx, SUB_SOURCE_DEMUX + 1); + lang_tag = 0; + break; + } + } mpctx->global_sub_pos += 2; mpctx->global_sub_pos = (mpctx->global_sub_pos % (global_sub_size + 1)) - 1; + lang_tag = 0; break; case M_PROPERTY_STEP_DOWN: + if (sub_source(mpctx) == SUB_SOURCE_DEMUX) { + // DEMUXER_CTRL_SWITCH_SUB only supports STEP_UP.... + sh_sub_t *sh = d_sub->sh; + if (sh && sh->lang2 && sh->cur_lang_tag == 1) { + lang_tag = 0; + break; + } + } mpctx->global_sub_pos += global_sub_size + 1; mpctx->global_sub_pos = (mpctx->global_sub_pos % (global_sub_size + 1)) - 1; + lang_tag = 1; break; default: return M_PROPERTY_NOT_IMPLEMENTED; @@ -1662,6 +1699,14 @@ static int mp_property_sub(m_option_t *prop, int action, void *arg, else if (ass_enabled) ass_track = sh->ass_track; #endif + if (sh->lang2 && lang_tag == 1) + sh->cur_lang_tag = 1; + else if (lang_tag == 0) + sh->cur_lang_tag = 0; + // don't touch sh->cur_lang_tag if lang_tag == -1 + mp_msg(MSGT_CPLAYER, MSGL_V, + "switched to sub_demux:%d[0x%04x] lang:%d\n", + d_sub->id, sh->sid, sh->cur_lang_tag); } else { d_sub->id = -2; d_sub->sh = NULL; @@ -1771,6 +1816,7 @@ static int mp_property_sub_by_type(m_option_t *prop, int action, void *arg, MPContext *mpctx) { int source, is_cur_source, offset, new_pos; + sh_sub_t *sh; update_global_sub_size(mpctx); if (!mpctx->sh_video || mpctx->global_sub_size <= 0) return M_PROPERTY_UNAVAILABLE; @@ -1818,8 +1864,22 @@ static int mp_property_sub_by_type(m_option_t *prop, int action, void *arg, int index = *(int *)arg; if (source == SUB_SOURCE_VOBSUB) index = vobsub_get_index_by_id(vo_vobsub, index); + else if (source == SUB_SOURCE_DEMUX) { + int i; + for (i = 0; i < MAX_S_STREAMS; i++) + if (mpctx->demuxer->s_streams[i] && + ((sh_sub_t *)mpctx->demuxer->s_streams[i])->sid == index) + break; + index = (i < MAX_S_STREAMS) ? i : -1; + mp_msg(MSGT_CPLAYER, MSGL_V, + "mp_property_sub_by_type converted arg:%d to index %d.\n", + *(int *)arg, index); + } new_pos = offset + index; if (index < 0 || index > mpctx->sub_counts[source]) { + mp_msg(MSGT_CPLAYER, MSGL_INFO, + "mp_property_sub_by_type: invalid index:%d/%d.\n", + index, mpctx->sub_counts[source]); new_pos = -1; *(int *) arg = -1; } @@ -1828,13 +1888,32 @@ static int mp_property_sub_by_type(m_option_t *prop, int action, void *arg, new_pos = -1; break; case M_PROPERTY_STEP_UP: + if (source == SUB_SOURCE_DEMUX) { + int index = -1; + if (demux_control(mpctx->demuxer, DEMUXER_CTRL_SWITCH_SUB, &index) + == DEMUXER_CTRL_OK) { + if (index == -1) { + new_pos = -1; + break; + } + index += offset; + return mp_property_sub(prop, M_PROPERTY_SET, &index, mpctx); + } + } + // falls through intentionally case M_PROPERTY_STEP_DOWN: { int step_all = (arg && *(int*)arg != 0 ? *(int*)arg : 1) * (action == M_PROPERTY_STEP_UP ? 1 : -1); int step = (step_all > 0) ? 1 : -1; int max_sub_pos_for_source = -1; + int lang_tag = -1; if (!is_cur_source) new_pos = -1; + else { + sh = mpctx->demuxer->s_streams[new_pos - offset]; + if (sh && sh->type == 'j') + lang_tag = sh->cur_lang_tag; + } while (step_all) { if (new_pos == -1) { if (step > 0) @@ -1843,23 +1922,51 @@ static int mp_property_sub_by_type(m_option_t *prop, int action, void *arg, // Find max pos for specific source new_pos = mpctx->global_sub_size - 1; while (new_pos >= 0 - && sub_source(mpctx) != source) + && sub_source_by_pos(mpctx, new_pos) != source) new_pos--; // cache for next time max_sub_pos_for_source = new_pos; } else new_pos = max_sub_pos_for_source; + lang_tag = -1; } else { + if (source == SUB_SOURCE_DEMUX) { + sh = mpctx->demuxer->s_streams[new_pos - offset]; + if (sh && sh->type == 'j' && sh->lang2) { + // two sub-streams are contained in this stream. + if (step > 0 && lang_tag != 1) { + // next one is the same stream with lang-tag:1 + lang_tag = 1; + step_all -= step; + continue; + } else if (step < 0 && lang_tag != 0) { + // next one is the same stream with lang-tag:0 + lang_tag = 0; + step_all -= step; + continue; + } + + // next one is another/different stream, but + // we don't know if it has sub-streams or not. + lang_tag = -1; + // fall through... + } + } new_pos += step; if (new_pos < offset || new_pos >= mpctx->global_sub_size || - sub_source(mpctx) != source) + sub_source_by_pos(mpctx, new_pos) != source) new_pos = -1; } step_all -= step; } + if (new_pos != -1) { + sh = mpctx->demuxer->s_streams[new_pos - offset]; + if (sh) + sh->cur_lang_tag = (sh->type == 'j' && lang_tag > 0) ? lang_tag : 0; + } break; } default: diff --git a/libmpdemux/demux_ts.c b/libmpdemux/demux_ts.c index 1b215124e..3f2ebe27a 100644 --- a/libmpdemux/demux_ts.c +++ b/libmpdemux/demux_ts.c @@ -97,6 +97,7 @@ typedef enum SPU_DVB = 0x3000001, SPU_TELETEXT = 0x3000002, SPU_PGS = 0x3000003, + SPU_ISDB = 0x3000010, PES_PRIVATE1 = 0xBD00000, SL_PES_STREAM = 0xD000000, SL_SECTION = 0xD100000, @@ -117,7 +118,7 @@ typedef struct { int pid; int ecm_pid; char lang[4]; - // copy from PMT + // copy from EIT, for audio / subpicture char lang2[4]; int is_dualmono; int component_tag; @@ -336,6 +337,7 @@ static int IS_SUB(es_stream_type_t type) case SPU_DVB: case SPU_PGS: case SPU_TELETEXT: + case SPU_ISDB: return 1; } return 0; @@ -379,6 +381,25 @@ static uint8_t get_packet_size(const unsigned char *buf, int size) return TS_PH_PACKET_SIZE; } +static void sub_set_lang_tag(sh_sub_t *sh) +{ + if (sh && sh->type == 'j' && dvdsub_lang && sh->lang && sh->lang2) { + char *p1, *p2; + + p1 = strstr(dvdsub_lang, sh->lang); + p2 = strstr(dvdsub_lang, sh->lang2); + + if (p2 != NULL && (p1 == NULL || p2 < p1)) { + mp_msg(MSGT_DEMUX, MSGL_V, + "setting lang. tag of sub pid[0x%04x] to 1.\n", + sh->sid); + sh->cur_lang_tag = 1; + return; + } + } + sh->cur_lang_tag = 0; +} + static int parse_avc_sps(uint8_t *buf, int len, int *w, int *h); static uint8_t *pid_lang_from_pmt(ts_priv_t *priv, int pid); @@ -392,11 +413,12 @@ static void ts_add_stream(demuxer_t * demuxer, ES_stream_t *es) sh_common_t *sh; int ctag; + // for ISDB, tag and lang(s) might have been set by EIT... ctag = es->component_tag; sh = priv->ts.streams[es->pid].sh; sh->default_track = ctag == 0x00 || ctag == 0x10 || ctag == 0x30; - if (es->lang[0]) { + if (es->lang[0] && (!sh->lang || strcmp(sh->lang, es->lang))) { free(sh->lang); sh->lang = strdup(es->lang); } @@ -405,8 +427,20 @@ static void ts_add_stream(demuxer_t * demuxer, ES_stream_t *es) (IS_AUDIO(es->type) || IS_AUDIO(es->subtype))) { sh_audio_t *sh_audio = (sh_audio_t *) sh; - free(sh_audio->lang2); - sh_audio->lang2 = strdup(es->lang2); + if (!sh_audio->lang2 || strcmp(sh_audio->lang2, es->lang2)) { + free(sh_audio->lang2); + sh_audio->lang2 = strdup(es->lang2); + } + } + if (es->type == SPU_ISDB) { + sh_sub_t *sh_sub = (sh_sub_t *) sh; + + if (!sh_sub->lang2 || strcmp(sh_sub->lang2, es->lang2)) { + free(sh_sub->lang2); + sh_sub->lang2 = es->lang2[0] ? + strdup(es->lang2) : NULL; + } + sub_set_lang_tag(sh_sub); } return; } @@ -489,13 +523,22 @@ static void ts_add_stream(demuxer_t * demuxer, ES_stream_t *es) sh->type = 'p'; break; case SPU_TELETEXT: sh->type = 'd'; break; + case SPU_ISDB: + sh->type = 'j'; break; } + sh->format = es->type; sh->default_track = (es->component_tag == 0x30); if (es->lang[0]) sh->lang = strdup(es->lang); + if (es->type == SPU_ISDB) { + if (es->lang2[0]) + sh->lang2 = strdup(es->lang2); + sub_set_lang_tag(sh); + } priv->ts.streams[es->pid].id = priv->last_sid; priv->ts.streams[es->pid].sh = sh; priv->ts.streams[es->pid].type = TYPE_SUB; + mp_msg(MSGT_DEMUX, MSGL_V, "\r\nADDED SUB PID %d, type: %x stream n. %d\r\n", es->pid, sh->format, priv->last_sid); priv->last_sid++; } } @@ -699,7 +742,8 @@ static int pes_match_lang(struct pmt_es_t *pes, char *lang) while ((len = strcspn(lang, ",")) > 0) { if (! strncmp(pes->lang, lang, len)) return 1; - if (pes->is_dualmono && ! strncmp(pes->lang2, lang, len)) + if ((pes->is_dualmono || pes->type == SPU_ISDB) && + ! strncmp(pes->lang2, lang, len)) return 2; lang += len; lang += strspn(lang, ","); @@ -825,7 +869,7 @@ static off_t ts_detect_streams(demuxer_t *demuxer, tsdemux_init_t *param) continue; if(is_video && req_vpid==-2) continue; - if(is_sub && req_spid==-2) + if(is_sub && req_spid<0) continue; chosen_pid = 0; @@ -862,7 +906,7 @@ static off_t ts_detect_streams(demuxer_t *demuxer, tsdemux_init_t *param) if(param->apid < 0 && param->vpid < 0 && - param->spid < 0 && + param->spid <= 0 && prog_candidate <= 0) { mp_msg(MSGT_IDENTIFY, MSGL_V, @@ -879,7 +923,7 @@ static off_t ts_detect_streams(demuxer_t *demuxer, tsdemux_init_t *param) if(p != -1) { has_tables++; - if(!param->prog && chosen_pid) + if(param->prog <= 0 && chosen_pid) prog_candidate = p; } @@ -973,6 +1017,8 @@ static off_t ts_detect_streams(demuxer_t *demuxer, tsdemux_init_t *param) // assert(chosen_pid || req_spid <= 0) if(chosen_pid) // (req_spid == es.pid) selected = 1; +// sub should not be auto-selected by default. +#if 0 else { // (req_spid != es.pid) && (req_spid <= 0) // sid not specified by user if (!low_cn && pes && pes->highCN_pid != 0) @@ -1004,12 +1050,13 @@ static off_t ts_detect_streams(demuxer_t *demuxer, tsdemux_init_t *param) else if(pes->component_tag < spes->component_tag) selected = 1; } +#endif if(selected) { if (param->spid != es.pid || (pes && !spes)) mp_msg(MSGT_IDENTIFY, MSGL_V, - "sub found! 0x%04hx tag:0x%02x" + "New sub found! 0x%04hx tag:0x%02x" " in [0x%04x].\n", es.pid, pes ? pes->component_tag : 0xff, prog_candidate); @@ -1179,7 +1226,11 @@ static off_t ts_detect_streams(demuxer_t *demuxer, tsdemux_init_t *param) } if(IS_SUB(param->stype)) - mp_msg(MSGT_DEMUXER, MSGL_INFO, " SUB %s(pid=%d) ", (param->stype==SPU_DVD ? "DVD" : param->stype==SPU_DVB ? "DVB" : "Teletext"), param->spid); + mp_msg(MSGT_DEMUXER, MSGL_INFO, " SUB %s(pid=%d) ", + (param->stype==SPU_DVD ? "DVD" : + param->stype==SPU_DVB ? "DVB" : + param->stype==SPU_ISDB ? "ISDB" : "Teletext"), + param->spid); else { param->stype = UNKNOWN; @@ -1376,7 +1427,16 @@ static demuxer_t *demux_open_ts(demuxer_t * demuxer) demuxer->audio->sh = NULL; } - if (params.stype == UNKNOWN) { + if (params.stype != UNKNOWN) { + sh_sub_t *sh_sub; + ts_add_stream(demuxer, priv->ts.pids[params.spid]); + sh_sub = priv->ts.streams[params.spid].sh; + demuxer->sub->id = priv->ts.streams[params.spid].id; + sh_sub->ds = demuxer->sub; + sh_sub->format = params.stype; + demuxer->sub->sh = sh_sub; + } + else { demuxer->sub->id = -2; demuxer->sub->sh = NULL; } @@ -1737,7 +1797,8 @@ static int pes_parse2(unsigned char *buf, uint16_t packet_len, ES_stream_t *es, pes_is_aligned = (p[6] & 4); stream_id = p[3]; - + if (stream_id == 0xBF) // 0xBF incl. ISDB super(impose) has no extension + return 0; if (p[7] & 0x80) { /* pts available */ @@ -1825,6 +1886,15 @@ static int pes_parse2(unsigned char *buf, uint16_t packet_len, ES_stream_t *es, return 1; } + else if (type_from_pmt == SPU_ISDB || + (packet_len >= 2 && p[0] == 0x80 && p[1] == 0xff)) + { + es->start = p; + es->size = packet_len; + es->type = SPU_ISDB; + es->payload_size -= packet_len; + return 1; + } else if (pes_is_aligned && packet_len >= 1 && ((p[0] & 0xE0) == 0x20)) //SPU_DVD { //DVD SUBS @@ -2062,7 +2132,7 @@ static int collect_section(ts_section_t *section, int is_start, unsigned char *b uint16_t tlen; int skip, tid; - mp_msg(MSGT_DEMUX, MSGL_V, "COLLECT_SECTION, start: %d, size: %d, collected: %d\n", is_start, size, section->buffer_len); + mp_msg(MSGT_DEMUX, MSGL_DBG2, "COLLECT_SECTION, start: %d, size: %d, collected: %d\n", is_start, size, section->buffer_len); if(! is_start && !section->buffer_len) return 0; @@ -2115,7 +2185,7 @@ static int collect_section(ts_section_t *section, int is_start, unsigned char *b return 0; tlen = ((ptr[1] & 0x0f) << 8) | ptr[2]; - mp_msg(MSGT_DEMUX, MSGL_V, "SKIP: %d+1, TID: %d, TLEN: %d, COLLECTED: %d\n", skip, tid, tlen, section->buffer_len); + mp_msg(MSGT_DEMUX, MSGL_DBG2, "SKIP: %d+1, TID: %d, TLEN: %d, COLLECTED: %d\n", skip, tid, tlen, section->buffer_len); if(section->buffer_len < (skip+1+3+tlen)) { mp_msg(MSGT_DEMUX, MSGL_DBG2, "DATA IS NOT ENOUGH, NEXT TIME\n"); @@ -2903,6 +2973,14 @@ static int parse_descriptors(struct pmt_es_t *es, uint8_t *ptr) if (refpid != 0x1fff && ! (ptr[j+2] & 0x01)) es->highCN_pid = refpid; } + else if(ptr[j] == 0xFD) // Data Coding Method Descriptor + { + if (es->type == 6 && descr_len >= 2) { + uint16_t code = ptr[j+2] << 8 | ptr[j+3]; + if (code == 0x8 || code == 0x12) + es->type = SPU_ISDB; + } + } else mp_msg(MSGT_DEMUX, MSGL_DBG2, "Unknown descriptor 0x%x, SKIPPING\n", ptr[j]); @@ -3013,6 +3091,98 @@ static void parse_acomp(ts_priv_t * priv, pmt_t * pmt, unsigned char *buf) return; } +static void parse_dcomp(ts_priv_t * priv, pmt_t * pmt, unsigned char *buf) +{ + int len; + uint8_t component_tag; + int selector_len, num_lang, lang_tag; + int i; + sh_sub_t *sh_sub; + ES_stream_t *tss; + uint8_t *p; + + len = buf[1] + 2; + if (len < 11) + goto bad_data; + + component_tag = buf[4]; + if (component_tag < 0x30 || (buf[2] << 8 | buf[3]) != 0x0008) { + mp_msg(MSGT_DEMUX, MSGL_V, "skipped non ISDB SPU desc.\n"); + return; + } + + for (i = 0; i < pmt->es_cnt; i++) + if (pmt->es[i].component_tag == component_tag) + break; + if (i == pmt->es_cnt) { + mp_msg(MSGT_DEMUX, MSGL_V, + "no PES with the tag:0x%02hhx.\n", component_tag); + return; + } + + selector_len = buf[5]; + if (selector_len < 5 || selector_len != buf[6] * 4 + 1 || + selector_len + 11 < len) + goto bad_data; + + mp_msg(MSGT_DEMUX, MSGL_V, "spu pid:0x%04x", pmt->es[i].pid); + pmt->es[i].type = SPU_ISDB; + + pmt->es[i].lang[0] = 0; + pmt->es[i].lang2[0] = 0; + num_lang = buf[6]; + p = buf + 7; + while (num_lang-- > 0) { + lang_tag = *p >> 5; + if (lang_tag == 0) { + memcpy(pmt->es[i].lang, p + 1, 3); + pmt->es[i].lang[3] = 0; + mp_msg(MSGT_DEMUX, MSGL_V, " [0]:%s", pmt->es[i].lang); + } else if (lang_tag == 1) { + memcpy(pmt->es[i].lang2, p + 1, 3); + pmt->es[i].lang2[3] = 0; + mp_msg(MSGT_DEMUX, MSGL_V, " [1]:%s", pmt->es[i].lang2); + } + p += 4; + } + mp_msg(MSGT_DEMUX, MSGL_V, "\n"); + + tss = priv->ts.pids[pmt->es[i].pid]; + if (tss) { + tss->type = pmt->es[i].type; + tss->component_tag = component_tag; + memcpy(tss->lang, pmt->es[i].lang, 4); + memcpy(tss->lang2, pmt->es[i].lang2, 4); + } + sh_sub = priv->ts.streams[pmt->es[i].pid].sh; + if (sh_sub) { + free(sh_sub->lang); + sh_sub->lang = NULL; + free(sh_sub->lang2); + sh_sub->lang2 = NULL; + + free(sh_sub->extradata); + sh_sub->extradata = NULL; + sh_sub->extradata_len = 0; + + if (tss->lang[0]) + sh_sub->lang = strdup(tss->lang); + + if (tss->lang2[0]) + sh_sub->lang2 = strdup(tss->lang2); + + if (dvdsub_lang && pes_match_lang(&pmt->es[i], dvdsub_lang) == 2) + sh_sub->cur_lang_tag = 1; + else + sh_sub->cur_lang_tag = 0; + } + return; + +bad_data: + mp_msg(MSGT_DEMUX, MSGL_INFO, "Broken Data Contents Descriptor.\n"); + return; +} + // parse EIT to set audio language static int parse_eit(ts_priv_t * priv, int is_start, unsigned char *buff, int size) @@ -3088,6 +3258,8 @@ static int parse_eit(ts_priv_t * priv, int is_start, unsigned char *buff, int si break; if (base[0] == 0xC4) parse_acomp(priv, pmt, base); + else if (base[0] == 0xC7) + parse_dcomp(priv, pmt, base); dlen -= base[1] + 2; base += base[1] + 2; } @@ -3274,9 +3446,16 @@ static int parse_pmt(ts_priv_t * priv, uint16_t progid, uint16_t pid, int is_sta pmt->es[idx].component_tag = -1; pmt->es[idx].lang[0] = 0; + pmt->es[idx].lang2[0] = 0; pmt->es[idx].highCN_pid = 0; parse_descriptors(&pmt->es[idx], &es_base[5]); + // discard ISDB super(impose) + if (pmt->es[idx].type == SPU_ISDB && + pmt->es[idx].component_tag != 0x87 && + !(pmt->es[idx].component_tag >= 0x30 && + pmt->es[idx].component_tag < 0x38)) + pmt->es[idx].type = UNKNOWN; if (pmt->es[idx].es_ecm.pid < 8192) // per-stream ECM specified. es_ecm = pmt->es[idx].es_ecm; else @@ -3717,6 +3896,7 @@ static void reselect_streams(demuxer_t *demuxer) demuxer->sub->id = priv->ts.streams[pid].id; demuxer->sub->sh = priv->ts.streams[pid].sh; ds_free_packs(demuxer->sub); + sub_set_lang_tag(demuxer->sub->sh); mp_msg(MSGT_DEMUX, MSGL_INFO, "subpic stream pid: %#04hx -> %#04hx\n", prev_spid, pid); @@ -4211,8 +4391,7 @@ static int ts_parse(demuxer_t *demuxer , ES_stream_t *es, unsigned char *packet, if(is_start) { - mp_msg(MSGT_DEMUX, MSGL_DBG2, "IS_START\n"); - + mp_msg(MSGT_DEMUX, MSGL_DBG2, "IS_START %d\n", pid); len = pes_parse2(p, buf_size, es, pid_type, pmt, pid); if(! len) { @@ -4598,14 +4777,15 @@ static int demux_ts_control(demuxer_t *demuxer, int cmd, void *arg) { case DEMUXER_CTRL_SWITCH_AUDIO: case DEMUXER_CTRL_SWITCH_VIDEO: + case DEMUXER_CTRL_SWITCH_SUB: { void *sh = NULL; int i = 8192, n; - int reftype, areset = 0, vreset = 0; + int reftype, areset = 0, vreset = 0, sreset = 0; demux_stream_t *ds; int id_max; sh_common_t **streams; - int dmode = 0; + int dmode = 0, lang_tag = 0; if(cmd == DEMUXER_CTRL_SWITCH_VIDEO) { @@ -4617,6 +4797,16 @@ static int demux_ts_control(demuxer_t *demuxer, int cmd, void *arg) if (ds && ds->sh) i = ((sh_video_t *) ds->sh)->vid; } + else if (cmd == DEMUXER_CTRL_SWITCH_SUB) + { + reftype = TYPE_SUB; + ds = demuxer->sub; + sreset = 1; + id_max = priv->last_sid; + streams = (sh_common_t **)demuxer->s_streams; + if (ds && ds->sh) + i = ((sh_sub_t *) ds->sh)->sid; + } else { reftype = TYPE_AUDIO; @@ -4630,7 +4820,7 @@ static int demux_ts_control(demuxer_t *demuxer, int cmd, void *arg) n = *((int*)arg); if(n == -2) { - reset_fifos(demuxer, areset, vreset, 0); + reset_fifos(demuxer, areset, vreset, sreset); ds->id = -2; ds->sh = NULL; ds_free_packs(ds); @@ -4656,10 +4846,23 @@ static int demux_ts_control(demuxer_t *demuxer, int cmd, void *arg) dmode = 1; goto finish; } + else if (reftype == TYPE_SUB && + priv->ts.pids[i] && + priv->ts.pids[i]->type == SPU_ISDB && + priv->ts.pids[i]->lang2[0] && + ((sh_sub_t *) ds->sh)->cur_lang_tag == 0) { + sh = priv->ts.streams[i].sh; + lang_tag = 1; + goto finish; + } k = (es_idx + 1) % pmt->es_cnt; while(k != es_idx) { + if (k == 0 && reftype == TYPE_SUB) { + *((int*)arg) = -1; + return DEMUXER_CTRL_OK; + } if(priv->ts.streams[pmt->es[k].pid].type == reftype && (pmt->es[k].highCN_pid == 0) ^ low_cn) { @@ -4669,6 +4872,22 @@ static int demux_ts_control(demuxer_t *demuxer, int cmd, void *arg) k = (k + 1) % pmt->es_cnt; } sh = priv->ts.streams[i].sh; + } else if (i == 8192 && reftype == TYPE_SUB) { + int k; + int pidx = progid_idx_in_pmt(priv, priv->prog); + if (pidx < 0) + return DEMUXER_CTRL_NOTIMPL; + pmt = &priv->pmt[pidx]; + for (k = 0; k < pmt->es_cnt; k++) + if (priv->ts.streams[pmt->es[k].pid].type == reftype && + (pmt->es[k].highCN_pid == 0) ^ low_cn) + break; + if (k == pmt->es_cnt) { + *((int*)arg) = -1; + return DEMUXER_CTRL_OK; + } + i = pmt->es[k].pid; + sh = priv->ts.streams[i].sh; } else { if (i >= 8192 || ds == NULL || ds->id < 0) return DEMUXER_CTRL_NOTIMPL; @@ -4688,12 +4907,15 @@ static int demux_ts_control(demuxer_t *demuxer, int cmd, void *arg) if(sh) { if(ds->id != priv->ts.streams[i].id) - reset_fifos(demuxer, areset, vreset, 0); + reset_fifos(demuxer, areset, vreset, sreset); ds->id = priv->ts.streams[i].id; ds->sh = sh; ds_free_packs(ds); if (reftype == TYPE_AUDIO) ((sh_audio_t *)sh)->dualmono_mode = dmode; + else if (reftype == TYPE_SUB) + ((sh_sub_t *)sh)->cur_lang_tag = lang_tag; + mp_msg(MSGT_DEMUX, MSGL_V, "\r\ndemux_ts, switched to audio pid %d, id: %d, sh: %p\r\n", i, ds->id, sh); } diff --git a/libmpdemux/demuxer.c b/libmpdemux/demuxer.c index e6b6f8896..146d4c499 100644 --- a/libmpdemux/demuxer.c +++ b/libmpdemux/demuxer.c @@ -279,6 +279,7 @@ const char *sh_sub_type2str(int type) case 'b': return "dvb"; case 'd': return "dvb-teletext"; case 'p': return "hdmv pgs"; + case 'j': return "isdb"; } return "unknown"; } @@ -297,6 +298,10 @@ sh_sub_t *new_sh_sub_sid(demuxer_t *demuxer, int id, int sid, const char *lang) sh_sub_t *sh = calloc(1, sizeof(sh_sub_t)); demuxer->s_streams[id] = sh; sh->sid = sid; +#ifdef CONFIG_ASS + if (ass_enabled && ass_library) + sh->ass_track = ass_default_track(ass_library); +#endif mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_SUBTITLE_ID=%d\n", sid); if (lang && lang[0] && strcmp(lang, "und")) { sh->lang = strdup(lang); @@ -319,6 +324,7 @@ static void free_sh_sub(sh_sub_t *sh) ass_free_track(sh->ass_track); #endif free(sh->lang); + free(sh->lang2); #ifdef CONFIG_FFMPEG clear_parser((sh_common_t *)sh); #endif @@ -1871,6 +1877,8 @@ int demuxer_sub_lang(demuxer_t *d, int id, char *buf, int buf_len) sh = d->s_streams[id]; if (sh && sh->lang) { av_strlcpy(buf, sh->lang, buf_len); + if (sh->lang2 && sh->cur_lang_tag > 0) + av_strlcpy(buf, sh->lang2, buf_len); return 0; } req.type = stream_ctrl_sub; @@ -1906,6 +1914,7 @@ int demuxer_audio_track_by_lang(demuxer_t *d, char *lang) return -1; } +// XXX: s_streams[] are not filled at all when this func is called. int demuxer_sub_track_by_lang(demuxer_t *d, char *lang) { int i, len; @@ -1917,8 +1926,16 @@ int demuxer_sub_track_by_lang(demuxer_t *d, char *lang) start = d->sub->id < 0 ? 0 : d->sub->id; for (i = 0; i < MAX_S_STREAMS; ++i) { sh_sub_t *sh = d->s_streams[(start + i) % MAX_S_STREAMS]; - if (sh && sh->lang && strncmp(sh->lang, lang, len) == 0) + if (!sh) + continue; + if (sh->lang && strncmp(sh->lang, lang, len) == 0) { + sh->cur_lang_tag = 0; + return sh->sid; + } + if (sh->lang2 && strncmp(sh->lang2, lang, len) == 0) { + sh->cur_lang_tag = 1; return sh->sid; + } } lang += len; lang += strspn(lang, ","); @@ -1950,8 +1967,15 @@ int demuxer_default_sub_track(demuxer_t *d) start = d->sub->id < 0 ? 0 : d->sub->id; for (i = 0; i < MAX_S_STREAMS; ++i) { sh_sub_t *sh = d->s_streams[(start + i) % MAX_S_STREAMS]; - if (sh && sh->default_track) - return sh->sid; + if (sh && sh->default_track) { + sh->cur_lang_tag = 0; + if (dvdsub_lang && sh->lang && sh->lang2) { + char *p1 = strstr(dvdsub_lang, sh->lang); + char *p2 = strstr(dvdsub_lang, sh->lang2); + + sh->cur_lang_tag = p2 != NULL && (p1 == NULL || p2 < p1); + } + } } return -1; } diff --git a/libmpdemux/demuxer.h b/libmpdemux/demuxer.h index 811282035..9b03578f2 100644 --- a/libmpdemux/demuxer.h +++ b/libmpdemux/demuxer.h @@ -114,6 +114,7 @@ #define DEMUXER_CTRL_CORRECT_PTS 16 #define DEMUXER_CTRL_SET_LOWCN 100 +#define DEMUXER_CTRL_SWITCH_SUB 101 #define SEEK_ABSOLUTE (1 << 0) #define SEEK_FACTOR (1 << 1) diff --git a/libmpdemux/stheader.h b/libmpdemux/stheader.h index 185eeda15..c8e74d10b 100644 --- a/libmpdemux/stheader.h +++ b/libmpdemux/stheader.h @@ -128,6 +128,10 @@ typedef struct sh_sub { SH_COMMON int sid; char type; // t = text, v = VobSub, a = SSA/ASS + // for type == 'j' only. + char *lang2; + int cur_lang_tag; // which sub track/lang to use. 0:lang, 1:lang2 + unsigned char* extradata; // extra header data passed from demuxer int extradata_len; #ifdef CONFIG_ASS diff --git a/mp_msg.c b/mp_msg.c index ec29a8e42..faac1a463 100644 --- a/mp_msg.c +++ b/mp_msg.c @@ -164,6 +164,7 @@ static void print_msg_module(FILE* stream, int mod) "ASS", "LOADER", "STATUSLINE", + "DECSUB", }; int c2 = (mod + 1) % 15 + 1; diff --git a/mp_msg.h b/mp_msg.h index 69d36992f..7d7dc7e98 100644 --- a/mp_msg.h +++ b/mp_msg.h @@ -126,6 +126,8 @@ extern int verbose; #define MSGT_TELETEXT 46 // Teletext decoder +#define MSGT_DECSUB 47 + #define MSGT_MAX 64 diff --git a/mpcommon.c b/mpcommon.c index ae611bf7d..47b003b93 100644 --- a/mpcommon.c +++ b/mpcommon.c @@ -47,6 +47,7 @@ #include "sub/vobsub.h" #include "sub/av_sub.h" #include "sub/sub_cc.h" +#include "sub/isdbsubdec.h" #include "libmpcodecs/dec_teletext.h" #include "libavutil/intreadwrite.h" #include "m_option.h" @@ -57,6 +58,7 @@ double sub_last_pts = -303; #ifdef CONFIG_ASS ASS_Track* ass_track = 0; // current track to render #endif +int event_items = -1; sub_data* subdata = NULL; subtitle* vo_sub_last = NULL; @@ -164,7 +166,7 @@ void init_vo_spudec(struct stream *stream, struct sh_video *sh_video, struct sh_ static int is_text_sub(int type) { - return type == 't' || type == 'm' || type == 'a'; + return type == 't' || type == 'm' || type == 'a' || type == 'j'; } static int is_av_sub(int type) @@ -192,6 +194,8 @@ void update_subtitles(sh_video_t *sh_video, double refpts, demux_stream_t *d_dvd if (is_av_sub(type)) reset_avsub(d_dvdsub->sh); #endif + if (type == 'j') + isdbsub_reset(d_dvdsub->sh); subcc_reset(); } // find sub @@ -312,13 +316,20 @@ void update_subtitles(sh_video_t *sh_video, double refpts, demux_stream_t *d_dvd subcc_process_data(packet, len); continue; } + if (type == 'j') { + type = isdbsub_decode(d_dvdsub->sh, &packet, &len, &subpts, &endpts); + if (type <= 0) + continue; + // "type" should be set to 'a' + } #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) + if ((len > 10 && memcmp(packet, "Dialogue: ", 10) == 0) || + (len > 0 && packet[0] == '[')) ass_process_data(ass_track, packet, len); else ass_process_chunk(ass_track, packet, len, @@ -335,29 +346,62 @@ void update_subtitles(sh_video_t *sh_video, double refpts, demux_stream_t *d_dvd sub_clear_text(&tmp_subs, MP_NOPTS_VALUE); } } + if (packet) + free(packet); continue; } #endif if (subpts != MP_NOPTS_VALUE) { + unsigned char *q = packet; 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) + if (len > 0 && *p == '[') { + if ((p = strstr(packet, "[Events]\nFormat:")) != NULL) { + p += strlen("[Events]\nFormat:"); + for (event_items = 0; *p != '\0' && *p != '\n'; p++) + if (*p == ',') + event_items++; + mp_msg(MSGT_SUBREADER, MSGL_V, + "[osd] event items: %d\n", event_items); + } + p = strstr(packet, "Dialogue: "); + if (p == NULL) { + free(q); + continue; + } + } + if (len > 10 && memcmp(p, "Dialogue: ", 10) == 0) skip_commas = 9; + if (event_items >= 0) + skip_commas = event_items; for (i=0; i < skip_commas && *p != '\0'; p++) if (*p == ',') i++; - if (*p == '\0') /* Broken line? */ + if (*p == '\0') { /* Broken line? */ + free(q); continue; + } len -= p - packet; packet = p; + // hack for multi "Dialogue:" lines. + // fill '\n' to replace non text items. + while ((p = strchr(p, '\n'))) { + for (i=0; i < skip_commas && *p != '\0'; p++) { + if (*p == ',') + i++; + *p = '\n'; + } + } } if (endpts == MP_NOPTS_VALUE) endpts = subpts + 4; sub_add_text(&subs, packet, len, endpts, 1); set_osd_subtitle(&subs); + if (q) + free(q); } if (d_dvdsub->non_interleaved) ds_get_next_pts(d_dvdsub); diff --git a/mplayer.c b/mplayer.c index 75787374d..964c4829c 100644 --- a/mplayer.c +++ b/mplayer.c @@ -96,6 +96,7 @@ #include "stream/tv.h" #include "access_mpcontext.h" #include "sub/ass_mp.h" +#include "sub/isdbsubdec.h" #include "cfg-mplayer-def.h" #include "codec-cfg.h" #include "command.h" @@ -1949,7 +1950,8 @@ static int select_subtitle(MPContext *mpctx) // finally select subs by language and container hints if (dvdsub_id == -1 && dvdsub_lang) dvdsub_id = demuxer_sub_track_by_lang(mpctx->demuxer, dvdsub_lang); - if (dvdsub_id == -1) + if (dvdsub_id == -1 && + (mpctx->stream->type == STREAMTYPE_DVD || mpctx->stream->type == STREAMTYPE_DVDNAV)) dvdsub_id = demuxer_default_sub_track(mpctx->demuxer); if (dvdsub_id >= 0) { id = dvdsub_id; @@ -2229,7 +2231,7 @@ static int fill_audio_out_buffers(void) // as the ao got underrun and became empty, // data to refill the whole ao buffer will be read / consumed burstly, // which decreases the stream cache. - mpctx->demuxer->stalled_time = 1; +// mpctx->demuxer->stalled_time = 1; } while (1) { @@ -3622,6 +3624,7 @@ int main(int argc, char *argv[]) if ((stream_dump_type) && (stream_dump_type < 4)) { FILE *f; demux_stream_t *ds = NULL; + int is_isdbsub = 0; current_module = "dump"; // select stream to dump switch (stream_dump_type) { @@ -3646,6 +3649,8 @@ int main(int argc, char *argv[]) ds_free_packs(mpctx->d_sub); mpctx->d_sub->id = -2; } + if (stream_dump_type == 3 && ds->sh && ((sh_sub_t *)ds->sh)->type == 'j') + is_isdbsub = 1; // let's dump it! f = fopen(stream_dump_name, "wb"); if (!f) { @@ -3659,12 +3664,17 @@ int main(int argc, char *argv[]) int in_size = ds_get_packet_pts(ds, &start, &pts); if (is_at_end(mpctx, &end_at, pts)) break; + if (in_size > 0 && is_isdbsub) + if (isdbsub_decode(ds->sh, &start, &in_size, &pts, NULL) <= 0) + continue; if ((mpctx->demuxer->file_format == DEMUXER_TYPE_AVI || mpctx->demuxer->file_format == DEMUXER_TYPE_ASF || mpctx->demuxer->file_format == DEMUXER_TYPE_MOV) && stream_dump_type == 2) fwrite(&in_size, 1, 4, f); if (in_size > 0) { fwrite(start, in_size, 1, f); stream_dump_progress(in_size, mpctx->stream); + if (is_isdbsub) + free(start); } if (dvd_last_chapter > 0) { int cur_chapter = demuxer_get_current_chapter(mpctx->demuxer); diff --git a/sub/ass_mp.c b/sub/ass_mp.c index 2cf08693c..4063f22f8 100644 --- a/sub/ass_mp.c +++ b/sub/ass_mp.c @@ -336,10 +336,9 @@ static void eosd_ass_update(struct mp_eosd_source *src, const struct mp_eosd_set ASS_Image *aimg; struct mp_eosd_image *img; if (res->changed || !src->initialized || ass_force_reload) { - double dar = (double) (res->w - res->ml - res->mr) / (res->h - res->mt - res->mb); ass_configure(ass_renderer, res->w, res->h, res->unscaled); ass_set_margins(ass_renderer, res->mt + ass_top_margin, res->mb + ass_bottom_margin, res->ml, res->mr); - ass_set_aspect_ratio(ass_renderer, dar, (double)res->srcw / res->srch); + ass_set_storage_size(ass_renderer, res->srcw, res->srch); src->initialized = 1; ass_force_reload = 0; } diff --git a/sub/isdbsubdec.c b/sub/isdbsubdec.c new file mode 100644 index 000000000..d19760aa4 --- /dev/null +++ b/sub/isdbsubdec.c @@ -0,0 +1,2315 @@ +/* + * ISDB subtitle decoding + * Copyright (c) 2013 0p1pp1 + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include + +#include "ass_mp.h" +#include "mp_global.h" +#include "mp_msg.h" +#include "libmpdemux/stheader.h" +#include "sub/isdbsubdec.h" + +#include "libavutil/avstring.h" +#include "libavutil/crc.h" +#include "libavutil/intreadwrite.h" + +#undef AV_NOPTS_VALUE +#define AV_NOPTS_VALUE ((signed)INT64_C(0x8000000000000000)) + +#define ISDBSUB_DATA_ID 0x80 +#define ISDBSUB_DU_TYPE_TXT 0x20 +#define ISDBSUB_UNIT_SEP 0x1F +#define ISDBSUB_MGMNT_TIMEOUT (180 * 1000) +#define ISDBSUB_NO_DGID -1 +#define ISDBSUB_MAX_LANG 2 /* ARIB TR-B14/B15 */ + +#define IS_HORIZONTAL_LAYOUT(format) \ + ((format) == ISDBSUB_FMT_960H || (format) == ISDBSUB_FMT_720H) +#define LAYOUT_GET_WIDTH(format) \ + (((format) == ISDBSUB_FMT_960H || (format) == ISDBSUB_FMT_960V) ? 960 : 720) +#define LAYOUT_GET_HEIGHT(format) \ + (((format) == ISDBSUB_FMT_960H || (format) == ISDBSUB_FMT_960V) ? 540 : 480) + +#define MPEGTS_MAX_PTS (((2LL<<33) + 45)/90) + +#define RGBA(r,g,b,a) (((unsigned)(255 - (a)) << 24) | ((b) << 16) | ((g) << 8) | (r)) + +static const AVCRC *Crc_table; + +typedef uint32_t rgba; +static rgba Default_clut[128] = +{ + //0-7 + RGBA(0,0,0,255), RGBA(255,0,0,255), RGBA(0,255,0,255), RGBA(255,255,0,255), + RGBA(0,0,255,255), RGBA(255,0,255,255), RGBA(0,255,255,255), RGBA(255,255,255,255), + //8-15 + RGBA(0,0,0,0), RGBA(170,0,0,255), RGBA(0,170,0,255), RGBA(170,170,0,255), + RGBA(0,0,170,255), RGBA(170,0,170,255), RGBA(0,170,170,255), RGBA(170,170,170,255), + //16-23 + RGBA(0,0,85,255), RGBA(0,85,0,255), RGBA(0,85,85,255), RGBA(0,85,170,255), + RGBA(0,85,255,255), RGBA(0,170,85,255), RGBA(0,170,255,255), RGBA(0,255,85,255), + //24-31 + RGBA(0,255,170,255), RGBA(85,0,0,255), RGBA(85,0,85,255), RGBA(85,0,170,255), + RGBA(85,0,255,255), RGBA(85,85,0,255), RGBA(85,85,85,255), RGBA(85,85,170,255), + //32-39 + RGBA(85,85,255,255), RGBA(85,170,0,255), RGBA(85,170,85,255), RGBA(85,170,170,255), + RGBA(85,170,255,255), RGBA(85,255,0,255), RGBA(85,255,85,255), RGBA(85,255,170,255), + //40-47 + RGBA(85,255,255,255), RGBA(170,0,85,255), RGBA(170,0,255,255), RGBA(170,85,0,255), + RGBA(170,85,85,255), RGBA(170,85,170,255), RGBA(170,85,255,255), RGBA(170,170,85,255), + //48-55 + RGBA(170,170,255,255), RGBA(170,255,0,255), RGBA(170,255,85,255), RGBA(170,255,170,255), + RGBA(170,255,255,255), RGBA(255,0,85,255), RGBA(255,0,170,255), RGBA(255,85,0,255), + //56-63 + RGBA(255,85,85,255), RGBA(255,85,170,255), RGBA(255,85,255,255), RGBA(255,170,0,255), + RGBA(255,170,85,255), RGBA(255,170,170,255), RGBA(255,170,255,255), RGBA(255,255,85,255), + //64 + RGBA(255,255,170,255), + // 65-127 are caliculated later. +}; + +static const uint8_t * const Default_macro[16] = +{ + "\x1B\x24\x42\x1B\x29\x4A\x1B\x2A\x30\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x24\x42\x1B\x29\x31\x1B\x2A\x30\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x24\x42\x1B\x29\x20\x41\x1B\x2A\x30\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x28\x32\x1B\x29\x34\x1B\x2A\x35\x1B\x2B\x20\x70\x0F\x1B\x7D", + + "\x1B\x28\x32\x1B\x29\x33\x1B\x2A\x35\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x28\x32\x1B\x29\x20\x41\x1B\x2A\x35\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x28\x20\x41\x1B\x29\x20\x42\x1B\x2A\x20\x43\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x28\x20\x44\x1B\x29\x20\x45\x1B\x2A\x20\x46\x1B\x2B\x20\x70\x0F\x1B\x7D", + + "\x1B\x28\x20\x47\x1B\x29\x20\x48\x1B\x2A\x20\x49\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x28\x20\x4A\x1B\x29\x20\x4B\x1B\x2A\x20\x4C\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x28\x20\x4D\x1B\x29\x20\x4E\x1B\x2A\x20\x4F\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x24\x42\x1B\x29\x20\x42\x1B\x2A\x30\x1B\x2B\x20\x70\x0F\x1B\x7D", + + "\x1B\x24\x42\x1B\x29\x20\x43\x1B\x2A\x30\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x24\x42\x1B\x29\x20\x44\x1B\x2A\x30\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x28\x31\x1B\x29\x30\x1B\x2A\x4A\x1B\x2B\x20\x70\x0F\x1B\x7D", + "\x1B\x28\x4A\x1B\x29\x32\x1B\x2A\x20\x41\x1B\x2B\x20\x70\x0F\x1B\x7D" +}; + + +struct b24str_state +{ + int gl; /* index of the group invoked to GL */ + int gr; /* index of the group invoked to GR */ + int ss; /* flag if in SS2 or SS3. 2:SS2, 3:SS3 */ + + struct group + { + unsigned char mb; /* how many bytes one character consists of. */ + // code for character sets +#define CODE_ASCII ('\x40') +#define CODE_ASCII2 ('\x4A') +#define CODE_JISX0208 ('\x42') +#define CODE_JISX0213_1 ('\x51') +#define CODE_JISX0213_2 ('\x50') +#define CODE_JISX0201_KATA ('\x49') +#define CODE_MOSAIC_C ('\x34') +#define CODE_MOSAIC_D ('\x35') +#define CODE_EXT ('\x3B') +#define CODE_X_HIRA ('\x30') +#define CODE_X_HIRA_P ('\x37') +#define CODE_X_KATA ('\x31') +#define CODE_X_KATA_P ('\x38') +#define CODE_X_DRCS_MB ('\x40') +#define CODE_X_DRCS_MIN ('\x41') +#define CODE_X_DRCS_MAX ('\x4F') +#define CODE_X_MACRO ('\x70') + unsigned char code; /* character set that this group designates */ + } g[4]; +}; + +struct isdbsub_layout +{ + enum isdbsub_format { + ISDBSUB_FMT_960H = 0x08, + ISDBSUB_FMT_960V, + ISDBSUB_FMT_720H, + ISDBSUB_FMT_720V, + } format; + int is_profile_c; // profile C: "1seg". see ARIB TR-B14 3-4 + + // clipping area. + struct disp_area { + int x, y; + int w, h; + } display_area; + + // for tracking pen position + int font_size; // valid values: {16, 20, 24, 30, 36} (TR-B14/B15) + struct fscale { // in [percent] + int fscx, fscy; + } font_scale; // 1/2x1/2, 1/2*1, 1*1, 1*2, 2*1, 2*2 + struct spacing { + int col, row; + } cell_spacing; + + // internal use for tracking pen position/line break. + // Although texts are laid out by libass, + // we need to track pen position by ourselves + // in order to calculate line breaking positions and charcter/line spacing. + int prev_char_sep; + int prev_line_desc; + int prev_line_bottom; // offset from display_area.y, not from top-margin. + int line_desc; + int linesep_upper; + int line_height; + int line_width; // pen position x + int prev_break_idx; // ctx->text.buf[prev_break_idx] holds the previous "\N" + int shift_baseline; // special case where baseline should be shifted down ? + + int block_offset_h; // text[0].hspacing / 2 + int block_offset_v; // line[0].lspacing_upper + + int repeat_count; // -1: none, 0: until EOL, 1...i: repeat the next char i times + int in_combining; // bool + struct scroll_param { + enum {SCROLL_DIR_NONE, SCROLL_DIR_COLUMN, SCROLL_DIR_ROW} direction; + int rollout; // bool + int speed; // in pixel/sec + } scroll; +}; + + +typedef struct isdbsub_state { + int auto_display; // bool. forced to be displayed w/o user interaction + int rollup_mode; // bool + + uint8_t need_init; // bool + uint8_t clut_high_idx; // color = default_clut[high_idx << 8 | low_idx] + + uint32_t fg_color; + uint32_t bg_color; + uint32_t mat_color; + + struct isdbsub_layout layout_state; + struct b24str_state text_state; +} ISDBSubState; + + +typedef struct ISDBSubContext { + int last_mngmnt_id; // "data group id" of the last subtitle management data + int64_t last_mngmnt_pts; // time when the last mgmnt data was received + int64_t pts; + int64_t duration; + + enum { + ISDBSUB_TMD_FREE, + ISDBSUB_TMD_REALTIME, + ISDBSUB_TMD_OFFSET + } timing_mode; + + struct timecode { + int hour, min, sec, ms; + } offset, start; // currently unused. + + ISDBSubState default_states[ISDBSUB_MAX_LANG]; + + int lang_tag; // language tag of the currently decoding subtitle text data + ISDBSubState current_state; //modified default_state[lang_tag] + + + iconv_t iconv; + struct my_str { + char *buf; + size_t len; + size_t used; + size_t txt_tail; // tail of the text, excluding trailing control sequences. + } text; + + // for output + int is_style_inited; + char *script_info; + char *events; +} ISDBSubContext; + + +struct margins { + int l, r, v; +}; + + +static void memdump(int level, const char *buf, int buf_size) +{ + int i; + + for (i=0; i < buf_size; i++) { + mp_msg(MSGT_DECSUB, level, "%02hhx ", buf[i]); + if (i % 16 == 15) + mp_msg(MSGT_DECSUB, level, "\n"); + } + if (i % 16) + mp_msg(MSGT_DECSUB, level, "\n"); +} + +static char *my_astrconcat(char *a, char *b) +{ + char *ret; + + if (a == NULL) + return b ? strdup(b) : NULL; + if (b == NULL) + return a; + ret = av_asprintf("%s%s", a, b); + free(a); + return ret; +} + +static char *pts_to_str(int64_t t, char *buf) +{ + int ms10, sec, min, hour; + + if (t == AV_NOPTS_VALUE) + return "NOPTS"; + + ms10 = (t % 1000) / 10; + t /= 1000; + sec = t % 60; + t /= 60; + min = t % 60; + t /= 60; + hour = t; + snprintf(buf, 13, "%02d:%02d:%02d.%02d", hour, min, sec, ms10); + + return buf; +} + +// NOTE: state->layout_state.format must be set before calling this func. +static void init_layout(struct isdbsub_layout *l) +{ + l->font_size = 36; + l->display_area.x = 0; + l->display_area.y = 0; + switch (l->format) { + case ISDBSUB_FMT_960H: + l->display_area.w = 960; + l->display_area.h = 540; + l->cell_spacing.col = 4; + l->cell_spacing.row = 24; + break; + case ISDBSUB_FMT_960V: + l->display_area.w = 960; + l->display_area.h = 540; + l->cell_spacing.col = 12; + l->cell_spacing.row = 24; + break; + case ISDBSUB_FMT_720H: + l->display_area.w = 720; + l->display_area.h = 480; + l->cell_spacing.col = 4; + l->cell_spacing.row = 16; + break; + case ISDBSUB_FMT_720V: + l->display_area.w = 720; + l->display_area.h = 480; + l->cell_spacing.col = 8; + l->cell_spacing.row = 24; + break; + } + // profile C uses a fixed format, + // which does not define specific position or size and + // just requires to display texts in either 16x3 or 12x4 characters. + // we use ISDBSUB_FMT_960H for the base format. + if (l->is_profile_c) { + l->display_area.x = 160; + l->display_area.y = 360; + l->display_area.w = 640; + l->display_area.h = 180; + } +} + +static void reset_state(ISDBSubState *state) +{ + struct isdbsub_layout *l = &state->layout_state; + struct b24str_state *s = &state->text_state; + + state->need_init = 1; + state->clut_high_idx = 0; + state->fg_color = Default_clut[7]; // white + state->bg_color = Default_clut[8]; // translucent + state->mat_color = Default_clut[8]; // FIXME: should be set in init_layout()? + + l->block_offset_h = l->cell_spacing.col / 2; + l->block_offset_v = l->cell_spacing.row / 2; + + l->font_scale.fscx = 100; + l->font_scale.fscy = 100; + + l->prev_char_sep = 0; + l->prev_line_desc = 0; + l->prev_line_bottom = 0; // 0 means blcok_offset & pen_pos. are not defined yet. + l->line_height = 0; + l->line_width = 0; + l->line_desc = 0; + l->linesep_upper = 0; + l->prev_break_idx = 0; + + l->shift_baseline = 0; + l->repeat_count = -1; + l->in_combining = 0; + l->scroll.direction = SCROLL_DIR_NONE; + + s->gl = 0; // G0 + s->gr = 2; // G2 + s->ss = 0; // not in SS{2,3} + + s->g[0].mb = 2; + s->g[0].code = CODE_JISX0208; + s->g[1].mb = 1; + s->g[1].code = CODE_ASCII; + s->g[2].mb = 1; + s->g[2].code = CODE_X_HIRA; + s->g[3].mb = 1; + s->g[3].code = CODE_X_MACRO; + + // profile C uses different default. + if (l->is_profile_c) { + s->g[3].mb = 1; + s->g[3].code = CODE_X_DRCS_MIN; + s->gl = 3; + s->gr = 0; + } +} + +static void get_margins(ISDBSubContext *ctx, struct margins *m) +{ + struct isdbsub_layout *lstate = &ctx->current_state.layout_state; + + if (IS_HORIZONTAL_LAYOUT(lstate->format)) { + m->l = lstate->display_area.x + lstate->block_offset_h; + m->r = LAYOUT_GET_WIDTH(lstate->format) + - (lstate->display_area.x + lstate->display_area.w); + m->v = lstate->display_area.y + lstate->block_offset_v; + if (lstate->is_profile_c) + m->v = 0; + } else { + m->l = lstate->display_area.y + lstate->block_offset_v; + m->r = LAYOUT_GET_HEIGHT(lstate->format) + - (lstate->display_area.y + lstate->display_area.h); + m->v = LAYOUT_GET_WIDTH(lstate->format) + - (lstate->display_area.x + lstate->display_area.w) + + lstate->block_offset_h; + } +} + +static void clear_text(ISDBSubContext *ctx) +{ + ctx->text.used = 0; + ctx->text.txt_tail = 0; +} + +static void fixup_linesep(ISDBSubContext *ctx); + +static void append_event(ISDBSubContext *ctx) +{ + ISDBSubState *state = &ctx->current_state; + struct isdbsub_layout *l = &state->layout_state; + char start[16], end[16]; + struct margins m; + char effect[36], clipping[64]; + char *dialog; + char c0 = 0; + + if (ctx->pts == AV_NOPTS_VALUE || ctx->text.buf == NULL || !ctx->text.used) + return; + fixup_linesep(ctx); + + mp_msg(MSGT_DECSUB, MSGL_DBG2, "append_event: %lu\n", ctx->text.used); +#if 0 + if (state->rollup_mode) + ctx->duration = l->line_count * 5 * 1000; // FIXME + else if (l->scroll.direction != SCROLL_DIR_NONE && l->scroll.speed > 0) + ctx->duration = (display_area.w + (rollout ? text_box.w : 0)) * 1000 / scroll.speed + * (rollout ? 0 : 5000); +#endif + + if (ctx->duration <= 0) + ctx->duration = 5000; + else if (ctx->duration < 100) + ctx->duration = 100; + + + pts_to_str(ctx->pts, start); + pts_to_str(ctx->pts + ctx->duration, end); + + effect[0] = '\0'; + get_margins(ctx, &m); + if (state->rollup_mode) { + if (IS_HORIZONTAL_LAYOUT(ctx->current_state.layout_state.format)) + av_strlcatf(effect, sizeof(effect), "Scroll up;;;"); + else + av_strlcatf(effect, sizeof(effect), "Banner;;;;"); // LtoR + } else if ((IS_HORIZONTAL_LAYOUT(l->format) && + l->scroll.direction == SCROLL_DIR_COLUMN) || + (!IS_HORIZONTAL_LAYOUT(l->format) && + l->scroll.direction == SCROLL_DIR_ROW)) + av_strlcatf(effect, sizeof(effect), "Banner;%d;%d;0;%d", + 1000 / l->scroll.speed, + (l->scroll.direction == SCROLL_DIR_ROW), + !l->scroll.rollout); + else if (l->scroll.direction != SCROLL_DIR_NONE) + av_strlcatf(effect, sizeof(effect), "Scroll up;%d;%d;%d;%d", + IS_HORIZONTAL_LAYOUT(l->format) ? m.v : m.l, + l->display_area.y + l->display_area.h, + 1000 / l->scroll.speed, + !l->scroll.rollout); + + clipping[0] = '\0'; + av_strlcatf(clipping, sizeof(clipping), "{\\clip(%d,%d,%d,%d)}", + l->display_area.x, l->display_area.y, + l->display_area.x + l->display_area.w, + l->display_area.y + l->display_area.h); + + // control sequences for the next event may be appended. + if (ctx->text.txt_tail != ctx->text.used) { + c0 = ctx->text.buf[ctx->text.txt_tail]; + ctx->text.buf[ctx->text.txt_tail] = '\0'; + } + + dialog = av_asprintf("Dialogue: %s,%s,%s,%d,%d,%d,%s,%s%s\n", + l->is_profile_c ? "prof_c" : + (IS_HORIZONTAL_LAYOUT(l->format) ? "hstyle" : "vstyle"), + pts_to_str(ctx->pts, start), pts_to_str(ctx->pts + ctx->duration, end), + m.l, m.r, m.v, effect, clipping, ctx->text.buf); + + if (ctx->events) { + ctx->events = my_astrconcat(ctx->events, dialog); + free(dialog); + } else + ctx->events = dialog; + + ctx->pts += ctx->duration; + ctx->duration = 0; + state->need_init = 1; + + if (ctx->text.txt_tail != ctx->text.used) { + ctx->text.buf[ctx->text.txt_tail] = c0; + memmove(ctx->text.buf, ctx->text.buf + ctx->text.txt_tail, + ctx->text.used - ctx->text.txt_tail); + ctx->text.used -= ctx->text.txt_tail; + ctx->text.txt_tail = 0; + } +} + +static void reserve_buf(ISDBSubContext *ctx, size_t len) +{ + size_t blen; + + if (ctx->text.len >= ctx->text.used + len) + return; + + blen = ((ctx->text.used + len + 127) >> 7) << 7; + ctx->text.buf = av_realloc(ctx->text.buf, blen); + if (!ctx->text.buf) { + mp_msg(MSGT_DECSUB, MSGL_WARN, "out of memory for ctx->text.\n"); + return; + } + ctx->text.len = blen; + mp_msg(MSGT_DECSUB, MSGL_V, "expanded ctx->text(%lu)\n", blen); +} + +static int append_str(ISDBSubContext *ctx, const char *str) +{ + size_t tlen = strlen(str); + + reserve_buf(ctx, tlen + 1); // +1 for terminating '\0' + memcpy(ctx->text.buf + ctx->text.used, str, tlen); + ctx->text.used += tlen; + ctx->text.buf[ctx->text.used] = '\0'; + return 0; +} + +static int prepend_str(ISDBSubContext *ctx, const char *str) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + size_t tlen = strlen(str); + + reserve_buf(ctx, tlen + 1); // +1 for terminating '\0' + memmove(ctx->text.buf + tlen, ctx->text.buf, ctx->text.used); + memcpy(ctx->text.buf, str, tlen); + if (ls->prev_break_idx) + ls->prev_break_idx += tlen; + ctx->text.used += tlen; + ctx->text.buf[ctx->text.used] = '\0'; + return 0; +} + +static void do_output(ISDBSubContext *ctx, uint8_t **outbufp, int *osize) +{ + int header = 0; + + if (!ctx->script_info && !ctx->events) + return; + mp_msg(MSGT_DECSUB, MSGL_DBG2, "making output.\n"); + + if (ctx->script_info) { + *outbufp = my_astrconcat(*outbufp, ctx->script_info); + free(ctx->script_info); + ctx->script_info = NULL; + header = 1; + } + if (!ctx->is_style_inited) { // once +#define STYLE_PARAMS_FONT \ + ":lang=ja:spacing=dual:spacing=mono:spacing=charcell" +#define STYLE_PARAMS_COMMON \ + "&H00FFFFFF, &HFF000000, &HFF000000, &HFF000000, 1, 0, 0, 1\n" + + *outbufp = my_astrconcat(*outbufp, "[V4+ Styles]\n" + "Format: Name, Fontname, Fontsize, Alignment, " + "PrimaryColour, OutlineColour, BackColour, ClippingColour, " + "BorderStyle, Outline, Shadow, treat_fontname_as_pattern\n" + "Style: vstyle, @" STYLE_PARAMS_FONT ", 36, 9, " STYLE_PARAMS_COMMON + "Style: hstyle, " STYLE_PARAMS_FONT ", 36, 7, " STYLE_PARAMS_COMMON + "Style: prof_c, " STYLE_PARAMS_FONT ", 36, 2, " STYLE_PARAMS_COMMON); + +#undef STYLE_PARAMS_FONT +#undef STYLE_PARAMS_COMMON + ctx->is_style_inited = 1; + header = 1; + } + if (ctx->events || header) { + if (header) + *outbufp = my_astrconcat(*outbufp, "\n[Events]\n" + "Format: Style, Start, End, MarginL, MarginR, MarginV, " + "Effect, Text\n"); + *outbufp = my_astrconcat(*outbufp, ctx->events); + free(ctx->events); + ctx->events = NULL; + } + *osize = strlen(*outbufp); + mp_msg(MSGT_DECSUB, MSGL_V, "ass output:\n%s\n", *outbufp); +} + +static void set_format(ISDBSubContext *ctx) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + + clear_text(ctx); + reset_state(&ctx->current_state); + + free(ctx->script_info); + ctx->script_info = av_asprintf("[Script Info]\nScript Type: v4.00+\n" + "PlayDepth: 24\nPlayResX: %d\nPlayResY: %d\nLanguage: ja\n" + "WrapStyle: 2\nStreamingMode: 1\n\n", + LAYOUT_GET_WIDTH(ls->format), LAYOUT_GET_HEIGHT(ls->format)); +} + +static void set_color(ISDBSubContext *ctx, int whatcolor, uint32_t color) +{ + char fmt[32]; + + if (whatcolor < 1 || whatcolor > 5) + return; + + fmt[0] = '\0'; + av_strlcatf(fmt, sizeof(fmt), "{\\%dc&H%06X&\\%da&H%02X&}", + whatcolor, color & 0xffffff, whatcolor, color >> 24); + append_str(ctx, fmt); + mp_msg(MSGT_DECSUB, MSGL_DBG2, "(%d)-th color set to 0x%08X.\n", + whatcolor, color); +} + +static void set_font_scale(ISDBSubContext *ctx) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + char fmt[24]; + + fmt[0] = '\0'; + av_strlcatf(fmt, sizeof(fmt), "{\\fscx%d\\fscy%d}", + ls->font_scale.fscx, ls->font_scale.fscy); + append_str(ctx, fmt); + mp_msg(MSGT_DECSUB, MSGL_DBG2, "font scale: (%d, %d)\n", + ls->font_scale.fscx, ls->font_scale.fscy); +} + +static const uint8_t *get_csi_params(const uint8_t *q, + unsigned int *p1, unsigned int *p2) +{ + if (!p1) + return NULL; + + *p1 = 0; + for (; *q >= 0x30 && *q <= 0x39; q++) { + *p1 *= 10; + *p1 += *q - 0x30; + } + if (!p2) + return q; + *p2 = 0; + if (*q != 0x20 && *q != 0x3B) + return NULL; + for (q++; *q >= 0x30 && *q <= 0x39; q++) { + *p2 *= 10; + *p2 += *q - 0x30; + } + return q; +} + +// called at the start of sub-text or after each SWF/SDP/SDF occurrences. +// Define some style overrides at the start of Dialog line outputs, +// which are not defined in [style] nor ASS default. +// Note that SDP/SDF don't reset the other style parameters. +// Note2: multiple calls of this func safely overwrite the previous defs. +static void setup_line_head(ISDBSubContext *ctx) +{ + ISDBSubState *state = &ctx->current_state; + struct isdbsub_layout *ls = &state->layout_state; + char item[256]; + int cscale; + + if (IS_HORIZONTAL_LAYOUT(ls->format)) + cscale = ls->font_scale.fscx; + else + cscale = ls->font_scale.fscy; + + item[0] = '\0'; + // lsp will be corrected later. + // Note: ASS scales \fsp by font scale of the preceeding char. + av_strlcatf(item, sizeof(item), "{\\lsp0\\fsp%d}", ls->cell_spacing.col); + ls->prev_char_sep = ls->cell_spacing.col * cscale / 100; + + av_strlcatf(item, sizeof(item), "{\\fs%d}", ls->font_size); + if (ls->font_scale.fscx != 100) + av_strlcatf(item, sizeof(item), "{\\fscx%d}", ls->font_scale.fscx); + if (ls->font_scale.fscy != 100) + av_strlcatf(item, sizeof(item), "{\\fscy%d}", ls->font_scale.fscy); + + if (state->fg_color != 0x00FFFFFF) { + if ((state->fg_color & 0xFFFFFF) != 0xFFFFFF) + av_strlcatf(item, sizeof(item), "{\\1c&H%06X&}", + state->fg_color & 0xFFFFFF); + if ((state->fg_color >> 24) != 0x00) + av_strlcatf(item, sizeof(item), "{\\1a&H%02X&}", + state->fg_color >> 24); + } + if (state->bg_color != 0xFF000000) { + if ((state->bg_color & 0xFFFFFF) != 0) + av_strlcatf(item, sizeof(item), "{\\4c&H%06X&}", + state->bg_color & 0xFFFFFF); + if ((state->bg_color >> 24) != 0xFF) + av_strlcatf(item, sizeof(item), "{\\4a&H%02X&}", + state->bg_color >> 24); + } + if (state->mat_color != 0xFF000000) { + if ((state->mat_color & 0xFFFFFF) != 0) + av_strlcatf(item, sizeof(item), "{\\5c&H%06X&}", + state->mat_color & 0xFFFFFF); + if ((state->mat_color >> 24) != 0xFF) + av_strlcatf(item, sizeof(item), "{\\5a&H%02X&}", + state->mat_color >> 24); + } + + prepend_str(ctx, item); + state->need_init = 0; +} + +static void advance_by_pixels(ISDBSubContext *ctx, int csp) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + int cscale; + int csep_orig; + char tmp[32]; + + if (IS_HORIZONTAL_LAYOUT(ls->format)) + cscale = ls->font_scale.fscx; + else + cscale = ls->font_scale.fscy; + csep_orig = ls->cell_spacing.col * cscale / 100; + + tmp[0] = '\0'; + av_strlcatf(tmp, sizeof(tmp), "{\\fsp%d}\xe2\x80\x8b{\\fsp%d}", + csp * 100 / cscale, ls->cell_spacing.col); + append_str(ctx, tmp); + ls->line_width += csp; + ls->prev_char_sep = csep_orig; + mp_msg(MSGT_DECSUB, MSGL_DBG2, "advanced %d pixel using fsp.\n", csp); +} + +static void do_line_break(ISDBSubContext *ctx); + +// move pen position to (col, row) relative to display area's top left. +// Note 1: In vertical layout, coordinates are rotated 90 deg. +// on the display area's top right. +// Note 2: the cell includes line/char spacings in both sides. +static void move_penpos(ISDBSubContext *ctx, int col, int row) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + int csp_l, col_ofs; + int cur_bottom; + int cell_height; + int cell_desc; + + mp_msg(MSGT_DECSUB, MSGL_DBG2, "move pen pos. to (%d, %d).\n", col, row); + if (IS_HORIZONTAL_LAYOUT(ls->format)) { + // convert pen pos. to upper left of the cell. + cell_height = (ls->font_size + ls->cell_spacing.row) + * ls->font_scale.fscy / 100; + if (ls->font_scale.fscy == 200) + cell_desc = ls->cell_spacing.row / 2; + else + cell_desc = ls->cell_spacing.row * ls->font_scale.fscy / 200; + row -= cell_height; + + csp_l = ls->cell_spacing.col * ls->font_scale.fscx / 200; + if (ls->line_width == 0 && ls->prev_line_bottom == 0) + ls->block_offset_h = csp_l; + col_ofs = ls->block_offset_h; + } else { + cell_height = (ls->font_size + ls->cell_spacing.row) + * ls->font_scale.fscx / 100; + cell_desc = cell_height / 2; + row -= cell_height - cell_desc; + + csp_l = ls->cell_spacing.col * ls->font_scale.fscy / 200; + if (ls->line_width == 0 && ls->prev_line_bottom == 0) + ls->block_offset_v = csp_l; + col_ofs = ls->block_offset_v; + } + + cur_bottom = ls->prev_line_bottom + + ls->linesep_upper + ls->line_height + ls->line_desc; + // allow adjusting +- cell_height/2 at maximum + // to align to the current line bottom. + if (row + cell_height / 2 > cur_bottom) { + // (col, row) is below the current line bottom + + do_line_break(ctx); // ls->prev_line_bottom == cur_bottom + ls->linesep_upper = row + cell_height - cell_desc - ls->prev_line_bottom; + ls->line_height = 0; + + advance_by_pixels(ctx, col + csp_l - col_ofs); + } else if (row + cell_height * 3 / 2 > cur_bottom && + col + csp_l > col_ofs + ls->line_width) { + // append to the current line... + advance_by_pixels(ctx, col + csp_l - (col_ofs + ls->line_width)); + } else { + mp_msg(MSGT_DECSUB, MSGL_INFO, "backward move not supported.\n"); + return; + } +} + +static void set_position(ISDBSubContext *ctx, unsigned int p1, unsigned int p2) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + int cw, ch; + int col, row; + + + if (IS_HORIZONTAL_LAYOUT(ls->format)) { + cw = (ls->font_size + ls->cell_spacing.col) * ls->font_scale.fscx / 100; + ch = (ls->font_size + ls->cell_spacing.row) * ls->font_scale.fscy / 100; + // pen position is at bottom left + col = p2 * cw; + row = p1 * ch + ch; + } else { + cw = (ls->font_size + ls->cell_spacing.col) * ls->font_scale.fscy / 100; + ch = (ls->font_size + ls->cell_spacing.row) * ls->font_scale.fscx / 100; + // pen position is at upper center, + // but in -90deg rotated coordinates, it is at middle left. + col = p2 * cw; + row = p1 * ch + ch / 2; + } + move_penpos(ctx, col, row); +} + +static void forward_position(ISDBSubContext *ctx, int rows, int cols); + +static const uint8_t *proc_ctl(ISDBSubContext *ctx, + const uint8_t *buf, int buf_size) +{ + ISDBSubState *state = &ctx->current_state; + struct b24str_state *ts = &state->text_state; + struct isdbsub_layout *ls = &state->layout_state; + uint8_t code; + unsigned int p1, p2; + int i, mb; + const uint8_t *q; + + while (buf_size > 0) { + code = buf[0]; + if ((code & 0x60) != 0) + break; + + buf++; + buf_size--; + p1 = p2 = 0; + switch (code) { + case 0x00: // NULL + break; + + // cursor + case 0x09: // APF + // append a space + forward_position(ctx, 0, 1); + break; + case 0x16: // PAPF (0x40..0x7F) + if (buf_size < 1) + return NULL; + p1 = buf[0] & 0x3F; + buf++; + buf_size--; + forward_position(ctx, 0, p1); + break; + case 0x0D: // APR + forward_position(ctx, 1, 0); + mp_msg(MSGT_DECSUB, MSGL_DBG2, "explicit line break.\n"); + break; + case 0x1C: // APS (0x40..0x7F, 0x40..0x7F) + if (buf_size < 2) + return NULL; + p1 = buf[0] & 0x3F; + p2 = buf[1] & 0x3F; + buf += 2; + buf_size -= 2; + mp_msg(MSGT_DECSUB, MSGL_DBG2, "aps: (%d, %d).\n", p1, p2); + set_position(ctx, p1, p2); + break; + + case 0x0C: // CS + if (!state->rollup_mode) { + init_layout(ls); + reset_state(state); + clear_text(ctx); + mp_msg(MSGT_DECSUB, MSGL_DBG2, "screen cleared.\n"); + } + break; + + // iso2022 text state + case 0x0E: // LS1 + ts->ss = 0; + ts->gl = 1; + break; + case 0x0F: // LS0 + ts->ss = 0; + ts->gl = 0; + break; + case 0x19: // SS2 + ts->ss = 2; + break; + case 0x1D: // SS3 + ts->ss = 3; + break; + case 0x1B: // ESC(....) + if (buf_size < 1) + return NULL; + p1 = buf[0]; + buf++; + buf_size--; + mb = 1; + switch (p1) { + case 0x6E: // LS2 + case 0x6F: // LS3 + ts->gl = 2 + (p1 & 0x01); + break; + case 0x7E: // LS1R + case 0x7D: // LS2R + case 0x7C: // LS3R + ts->gr = 0x7F - p1; + break; + + // 0x28-0x2B (F) designate 1B set to G0-G3 + // 0x24 [0x29-2B] (F) designate 2B set to G0-G3 + // 0x28-0x2B 0x20 (F) designate 1B DRCS/MACRO to G0-G3 + // 0x24 0x28-0x2B 0x20 (F) designate 2B DRCS to G0-G3 + case 0x24: + if (buf_size < 1) + return NULL; + mb = 2; + p1 = buf[0]; + if (p1 >= 0x28 && p1 <= 0x2B) { + buf++; + buf_size--; + } else + p1 = 0x28; + // fall through + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + if (buf_size < 1) + return NULL; + p2 = buf[0]; + buf++; + buf_size--; + if (p2 == 0x20) { + if (buf_size < 1) + return NULL; + p2 = buf[0]; + buf++; + buf_size--; + } + ts->g[p1 - 0x28].mb = mb; + ts->g[p1 - 0x28].code = p2; + break; + default: + mp_msg(MSGT_DECSUB, MSGL_V, + "unknown escape sequence: 0x%02hhx\n", p1); + } + break; + + // color + case 0x80: // BKF + case 0x81: // RDF + case 0x82: // GRF + case 0x83: // YLF + case 0x84: // BLF + case 0x85: // MGF + case 0x86: // CNF + case 0x87: // WHF + p1 = (state->clut_high_idx << 4) | (code & 0x0F); + state->fg_color = Default_clut[p1]; + set_color(ctx, 1, state->fg_color); + break; + case 0x90: // COL(0x48..0x7F) or (0x20, 0x40..0x4F) + if (buf_size < 1) + return NULL; + p1 = buf[0]; + buf++; + buf_size--; + if (p1 == 0x20) { + if (buf_size < 1) + return NULL; + state->clut_high_idx = (buf[0] & 0x0F); + buf++; + buf_size--; + break; + } + p2 = (state->clut_high_idx << 4) | (p1 & 0x0F); + if ((p1 & 0xF0) == 0x40) { + state->fg_color = Default_clut[p2]; + set_color(ctx, 1, state->fg_color); + } + break; + + // character size + case 0x88: // SSZ + ls->font_scale.fscx = 50; + ls->font_scale.fscy = 50; + set_font_scale(ctx); + break; + case 0x89: // MSZ + ls->font_scale.fscx = 50; + ls->font_scale.fscy = 100; + set_font_scale(ctx); + break; + case 0x8A: // NSZ + ls->font_scale.fscx = 100; + ls->font_scale.fscy = 100; + set_font_scale(ctx); + break; + case 0x8B: // SZX ({0x41,0x44,0x45}) + if (buf_size < 1) + return NULL; + p1 = buf[0]; + buf++; + buf_size--; + ls->font_scale.fscx = 100; + ls->font_scale.fscy = 100; + if ((p1 & 0xFB) == 0x41) + ls->font_scale.fscy = 200; + if ((p1 & 0xFE) == 0x44) + ls->font_scale.fscx = 200; + set_font_scale(ctx); + break; + + case 0x98: // RPC (0x40..0x7F) + if (buf_size < 1) + return NULL; + p1 = buf[0] & 0x3F; + buf++; + buf_size--; + ls->repeat_count = p1; + mp_msg(MSGT_DECSUB, MSGL_DBG2, "repeat char %d times.\n", p1); + break; + + case 0x99: // SPL + append_str(ctx, "{\\u0}"); + break; + case 0x9A: // STL + append_str(ctx, "{\\u1}"); + break; + + case 0x9B: // CSI + for (i = 0; i < buf_size; i++) + if (buf[i] >= 0x40 && buf[i] <= 0x6F) + break; + if (i == buf_size) { + buf += buf_size; + buf_size = 0; + break; + } + switch (buf[i]) { + // SWF1 (0x30..0x39, ...., 0x20, 0x53) + // -SWF2 ({0x38,0x3F?}, 0x3B, 0x30..0x32, 0x3B, 0x30..0x39, ...., [0x3B, 0x30..0x39,...,] 0x20, 0x53) + case 0x53: + q = get_csi_params(buf, &p1, NULL); + if (!q || *q != 0x20 || p1 < 7 || p1 > 10) + break; + ls->format = ISDBSUB_FMT_960H + (p1 - 7); + set_format(ctx); // calls reset_state, thus sate->need_init = 1 + mp_msg(MSGT_DECSUB, MSGL_DBG2, "new format: %d\n", ls->format); + break; + // SDF (0x30..0x39,..., 0x3B, 0x30..0x39,..., 0x20, 0x56) + case 0x56: + q = get_csi_params(buf, &p1, &p2); + if (!q || *q != 0x20) + break; + ls->display_area.w = p1; + ls->display_area.h = p2; + state->need_init = 1; + mp_msg(MSGT_DECSUB, MSGL_DBG2, + "new disp. area size: (%u, %u)\n", p1, p2); + break; + // SDP (0x30..0x39,..., 0x3B, 0x30..0x39,..., 0x20, 0x5F) + case 0x5F: + q = get_csi_params(buf, &p1, &p2); + if (!q || *q != 0x20) + break; + ls->display_area.x = p1; + ls->display_area.y = p2; + state->need_init = 1; + mp_msg(MSGT_DECSUB, MSGL_DBG2, + "new disp. area pos: (%u, %u)\n", p1, p2); + break; + + // RCS (0x30..0x39,...., 0x20, 0x6E) + case 0x6E: + q = get_csi_params(buf, &p1, NULL); + if (!q || *q != 0x20 || p1 > 15) + break; + state->mat_color = Default_clut[state->clut_high_idx << 4 | p1]; + set_color(ctx, 5, state->mat_color); + break; + + // SSM (0x30..0x39,..., 0x3B, 0x30..0x39,..., 0x20, 0x57) + case 0x57: + q = get_csi_params(buf, &p1, &p2); + if (!q || *q != 0x20 || p1 != p2) + break; + if (p1 == 16 || p1 == 20 || p1 == 24 || p1 == 30 || p1 == 36) { + char fs[8]; + + ls->font_size = p1; + fs[0] = '\0'; + av_strlcatf(fs, sizeof(fs), "{\\fs%d}", p1); + append_str(ctx, fs); + if (ls->line_width != 0) + ls->shift_baseline = 0; + mp_msg(MSGT_DECSUB, MSGL_DBG2, "font size:%d\n", p1); + } + break; + // SHS (0x30..0x39,..., 0x20, 0x58) + // SVS (0x30..0x39,..., 0x20, 0x59) + case 0x58: + case 0x59: + q = get_csi_params(buf, &p1, NULL); + if (!q || *q != 0x20) + break; + if (code == 0x58) + ls->cell_spacing.col = p1; + else { + ls->cell_spacing.row = p1; + if (ls->line_width != 0) + ls->shift_baseline = 0; + } + mp_msg(MSGT_DECSUB, MSGL_DBG2, "%c-spacing:%u.\n", + (code == 0x58) ? 'h' : 'v', p1); + // no output here. automatically inserted later in proc_char(). + break; + + // ORN (0x30, 0x20, 0x63) or (0x31, 0x3B, 0x30..0x39 * 4, 0x20, 0x63) + case 0x63: + q = get_csi_params(buf, &p1, &p2); + if (!q) + break; + if (p1 == 0) // no ornaments + append_str(ctx, "{\\bord0\\3a&HFF&\\3c&H000000&}"); + else if (p1 == 1 && *q == 0x20) { // outline + int idx = (p2 / 100) << 4 | (p2 % 100); + if (idx < 0 || idx > 127) + break; + append_str(ctx, "{\\bord1}"); + set_color(ctx, 3, Default_clut[idx]); + } + break; + + // SCR (0x30..0x34, 0x3B, 0x30..0x39..., 0x20, 0x67) + case 0x67: + q = get_csi_params(buf, &p1, &p2); + if (!q || *q != 0x20 || p1 > 4) + break; + if (p1 != 0) + clear_text(ctx); + ls->scroll.direction = (p1 + 1) / 2; + ls->scroll.rollout = (p1 == 2 || p1 == 4); + if (p2 == 0) + p2 = 1; + ls->scroll.speed = p2; + break; + + // ACPS (0x30..0x39,...., 0x3B, 0x30..0x39,...., 0x20, 0x61) + case 0x61: + q = get_csi_params(buf, &p1, &p2); + if (!q || *q != 0x20) + break; + if ((signed) p1 < ls->display_area.x + || (signed) p1 >= ls->display_area.x + ls->display_area.w + || (signed) p2 < ls->display_area.y + || (signed) p2 >= ls->display_area.y + ls->display_area.h) { + mp_msg(MSGT_DECSUB, MSGL_WARN, + "invalid parameters (%u, %u) in ACPS.\n", p1, p2); + break; + } + if (IS_HORIZONTAL_LAYOUT(ls->format)) + move_penpos(ctx, p1 - ls->display_area.x, + p2 - ls->display_area.y); + else + move_penpos(ctx, ls->display_area.w - (p1 - ls->display_area.x), + p2 - ls->display_area.y); + break; + + // -CCC ({0x30, 0x32..0x34}, 0x20, 0x54) + // -PLD (0x5B) + // -PLU (0x5C) + // -GSM (0x30..0x39,..., 0x3B, 0x30..0x39,..., 0x20, 0x42) + // -GAA ({0x30,0x31}, 0x20, 0x5D) + // -SRC (0x30..0x33, 0x3B, 0x30..0x39 * 4, 0x20, 0x5E) + // -TCC (0x30..0x3A, 0x3B, 0x30..0x33, 0x3B, 0x30..0x39,..., 0x20, 0x62) + // -CFS (0x30..0x39,..., 0x20, 0x65) + // -MDF (0x30..0x33, 0x20, 0x64) + // -XCS (0x30..0x31, 0x20, 0x66) + // PRA (0x30..0x39,..., 0x20, 0x68) + // -ACS (0x30, 0x20, 0x69)....(0x9B, 0x31,0x20,0x69) {(0x9B,{0x32,0x34},0x20,0x69)...(0x9B,{0x33,0x35},0x20, 0x69)}+ + // -SCS (0x6F)(0x9B,...).....(0x9B,0x6F) + default: + mp_msg(MSGT_DECSUB, MSGL_V, + "invalid/un-supported CSI. terminating code:0x%02hhx len:%d\n", buf[i], i); + } + i++; + buf += i; + buf_size -= i; + break; + + // non-supported control codes + case 0x9D: // -TIME (0x20, 0x40..0x7F) or (0x28, 0x40..0x43) or (0x29, ...., 0x40..0x43) + if (buf_size < 2) + return NULL; + if (buf[0] == 0x20 || buf[0] == 0x28) { + if (buf[0] == 0x20) { + // wait. copy & split out as another event + ctx->duration = (buf[1] - 0x40) * 100; + append_event(ctx); + } + buf += 2; + buf_size -= 2; + break; + } + if (buf[0] != 0x29) + return NULL; + while (buf_size > 0 && (buf[0] & 0xFC) != 0x40) { + buf++; + buf_size--; + } + if ((buf[0] & 0xFC) != 0x40) + return NULL; + // buf_size > 0 + buf++; + buf_size--; + mp_msg(MSGT_DECSUB, MSGL_V, + "un-supported control code: 0x%02hhx\n", code); + break; + + case 0x95: // -MACRO (0x4F) or (0x40..0x41, 0x21..0x7E, ...., 0x95, 0x4F) + if (buf_size < 1) + return NULL; + if (buf[0] == 0x40 || buf[0] == 0x41) { + while (buf_size > 1 && buf[0] != 0x95) { + buf++; + buf_size--; + } + if (buf[0] != 0x95) + return NULL; + // buf_size > 1 + buf++; + buf_size--; + } + if (buf[0] != 0x4F) + return NULL; + buf++; + buf_size--; + mp_msg(MSGT_DECSUB, MSGL_V, + "un-supported control code: 0x%02hhx\n", code); + break; + + case 0x92: // -CDC ({0x40,0x4F}) or (0x20, 0x40..0x4A) + if (buf_size >= 2 && buf[0] == 0x20) { + buf++; + buf_size--; + } + // fall through + case 0x91: // -FLC ({0x40,0x47,0x4F}) + case 0x93: // -POL (0x40..0x42) + case 0x94: // -WHM ({0x40,0x44,0x45}) + case 0x97: // -HLC (0x40..0x4F) + if (buf_size < 1) + return NULL; + buf++; + buf_size--; + // fall through + case 0x08: // APB + case 0x0A: // APD + case 0x0B: // -APU + default: // un-used/unknown control codes + mp_msg(MSGT_DECSUB, MSGL_V, + "unknown/ignored control code: 0x%02hhx\n", code); + break; + } + } + return buf; +} + +static const uint8_t * const combining1[] = { + "\xcc\x81", // U+0301 combinng accute accent + "\xcc\x80", // U+0300 combining grave accent + "\xcc\x88", // U+0308 combining diaeresis + "\xcc\x82", // U+0302 combining circumflex accent + "\xcc\x84", // U+0304 combining macron + "\xcc\xb2", // U+0332 combining low line +}; +static const uint8_t * const hira_symbols[9] = { + "\xe3\x82\x9d" /* U+309D */, "\xe3\x82\x9e" /* U+309E */, + "\xe3\x83\xbc" /* U+30FC */, "\xe3\x80\x82" /* U+3002 */, + "\xe3\x80\x8c" /* U+300C */, "\xe3\x80\x8d" /* U+300D */, + "\xe3\x80\x81" /* U+3001 */, "\xe3\x83\xbb" /* U+30FB */, +}; +static const uint8_t * const kata_symbols[9] = { + "\xe3\x83\xbd" /* U+30FD */, "\xe3\x83\xbe" /* U+30FE */, + "\xe3\x83\xbc" /* U+30FC */, "\xe3\x80\x82" /* U+3002 */, + "\xe3\x80\x8c" /* U+300C */, "\xe3\x80\x8d" /* U+300D */, + "\xe3\x80\x81" /* U+3001 */, "\xe3\x83\xbb" /* U+30FB */, +}; +/* ARIB STD B24 table.7-11 -> UTF-8 */ +static const uint8_t * const trans_ext85[] = { + /* 1- 10 */ + "\xe3\x90\x82", "\xf0\xa0\x85\x98", "\xe4\xbb\xbd", "\xe4\xbb\xbf", "\xe4\xbe\x9a", + "\xe4\xbf\x89", "\xe5\x82\x9c", "\xe5\x84\x9e", "\xe5\x86\xbc", "\xe3\x94\x9f", + /* 11- 20 */ + "\xe5\x8c\x87", "\xe5\x8d\xa1", "\xe5\x8d\xac", "\xe5\xa9\xb9", "\xf0\xa0\xae\xb7", + "\xe5\x91\x8d", "\xe5\x92\x96", "\xe5\x92\x9c", "\xe5\x92\xa9", "\xe5\x94\x8e", + /* 21- 30 */ + "\xe5\x95\x8a", "\xe5\x99\xb2", "\xe5\x9b\xa4", "\xe5\x9c\xb3", "\xe5\x9c\xb4", + "\xef\xa8\x90", "\xe5\xa2\x80", "\xe5\xa7\xa4", "\xe5\xa8\xa3", "\xe5\xa9\x95", + /* 31- 40 */ + "\xe5\xaf\xac", "\xef\xa8\x91", "\xe3\x9f\xa2", "\xe5\xba\xac", "\xe5\xbc\xb4", + "\xe5\xbd\x85", "\xe5\xbe\xb7", "\xe6\x80\x97", "\xe6\x81\xb5", "\xe6\x84\xb0", + /* 41- 50 */ + "\xe6\x98\xa4", "\xe6\x9b\x88", "\xe6\x9b\x99", "\xe6\x9b\xba", "\xe6\x9b\xbb", + "\xee\x82\x88", "\xe6\xa2\x81", "\xe6\xa4\x91", "\xe6\xa4\xbb", "\xe6\xa9\x85", + /* 51- 60 */ + "\xe6\xaa\x91", "\xe6\xab\x9b", "\xf0\xa3\x8f\x8c", "\xf0\xa3\x8f\xbe", "\xf0\xa3\x97\x84", + "\xe6\xaf\xb1", "\xe6\xb3\xa0", "\xe6\xb4\xae", "\xef\xa9\x85", "\xe6\xb6\xbf", + /* 61- 70 */ + "\xe6\xb7\x8a", "\xe6\xb7\xb8", "\xef\xa9\x86", "\xe6\xbd\x9e", "\xe6\xbf\xb9", + "\xe7\x81\xa4", "\xe7\x85\x95", "\xf0\xa4\x8b\xae", "\xe7\x85\x87", "\xe7\x87\x81", + /* 71- 80 */ + "\xe7\x88\x80", "\xe7\x8e\x9f", "\xe7\x8e\xa8", "\xe7\x8f\x89", "\xe7\x8f\x96", + "\xe7\x90\x9b", "\xe7\x90\xa1", "\xef\xa9\x8a", "\xe7\x90\xa6", "\xe7\x90\xaa", + /* 81- 90 */ + "\xe7\x90\xac", "\xe7\x90\xb9", "\xe7\x91\x8b", "\xe3\xbb\x9a", "\xe7\x95\xb5", + "\xe7\x96\x81", "\xe7\x9d\xb2", "\xe4\x82\x93", "\xe7\xa3\x88", "\xe7\xa3\xa0", + /* 91-100 */ + "\xe7\xa5\x87", "\xe7\xa6\xae", "\xe7\xa5\x93", "\xe8\xa2\x82", "\xe8\xa5\xa6", + "\xe7\xa7\x9a", "\xe7\xa8\x9e", "\xe7\xad\xbf", "\xe7\xb0\xb1", "\xe4\x89\xa4", + /* 101-110 */ + "\xe7\xb6\x8b", "\xe7\xbe\xa1", "\xe8\x84\x98", "\xe8\x84\xba", "\xe8\x88\x98", + "\xe8\x8a\xae", "\xe8\x91\x9b", "\xe8\x93\x9c", "\xe8\x93\xac", "\xe8\x95\x99", + /* 111-120 */ + "\xe8\x97\x8e", "\xe8\x9d\x95", "\xe8\x9f\xac", "\xe8\xa0\x8b", "\xe8\xa3\xb5", + "\xe8\xa7\x92", "\xe8\xab\xb6", "\xe8\xb7\x8e", "\xe8\xbe\xbb", "\xe8\xbf\xb6", + /* 121-130 */ + "\xe9\x83\x9d", "\xe9\x84\xa7", "\xe9\x84\xad", "\xe9\x86\xb2", "\xe9\x88\xb3", + "\xe9\x8a\x88", "\xe9\x8c\xa1", "\xe9\x8d\x88", "\xe9\x96\x92", "\xe9\x9b\x9e", + /* 131-137 */ + "\xe9\xa4\x83", "\xe9\xa5\x80", "\xe9\xab\x99", "\xe9\xaf\x96", "\xe9\xb7\x97", + "\xe9\xba\xb4", "\xe9\xba\xb5", +}; + +static const uint8_t * const trans_ext90[][96] = { + { /* row 90 */ + /* 1- 10 */ + "\xe2\x9b\x8c", "\xe2\x9b\x8d", "\xe2\x9d\x97", "\xe2\x9b\x8f", "\xe2\x9b\x90", + "\xe2\x9b\x91", "", "\xe2\x9b\x92", "\xe2\x9b\x95", "\xe2\x9b\x93", + /* 11- 20 */ + "\xe2\x9b\x94", "", "", "", "", "", + "\xf0\x9f\x85\xbf", "\xf0\x9f\x86\x8a", "", "", "\xe2\x9b\x96", + /* 21- 30 */ + "\xe2\x9b\x97", "\xe2\x9b\x98", "\xe2\x9b\x99", "\xe2\x9b\x9a", "\xe2\x9b\x9b", + "\xe2\x9b\x9c", "\xe2\x9b\x9d", "\xe2\x9b\x9e", "\xe2\x9b\x9f", "\xe2\x9b\xa0", + /* 31- 40 */ + "\xe2\x9b\xa1", "\xe2\xad\x95", "\xe3\x89\x88", "\xe3\x89\x89", "\xe3\x89\x8a", + "\xe3\x89\x8b", "\xe3\x89\x8c", "\xe3\x89\x8d", "\xe3\x89\x8e", "\xe3\x89\x8f", + /* 41- 50 */ + "", "", "", "", "\xe2\x92\x91", + "\xe2\x92\x92", "\xe2\x92\x93", "\xf0\x9f\x85\x8a", "\xf0\x9f\x85\x8c", "\xf0\x9f\x84\xbf", + /* 51- 60 */ + "\xf0\x9f\x85\x86", "\xf0\x9f\x85\x8b", "\xf0\x9f\x88\x90", "\xf0\x9f\x88\x91", "\xf0\x9f\x88\x92", + "\xf0\x9f\x88\x93", "\xf0\x9f\x85\x82", "\xf0\x9f\x88\x94", "\xf0\x9f\x88\x95", "\xf0\x9f\x88\x96", + /* 61- 70 */ + "\xf0\x9f\x85\x8d", "\xf0\x9f\x84\xb1", "\xf0\x9f\x84\xbd", "\xe2\xac\x9b", "\xe2\xac\xa4", + "\xf0\x9f\x88\x97", "\xf0\x9f\x88\x98", "\xf0\x9f\x88\x99", "\xf0\x9f\x88\x9a", "\xf0\x9f\x88\x9b", + /* 71- 80 */ + "\xe2\x9a\xbf", "\xf0\x9f\x88\x9c", "\xf0\x9f\x88\x9d", "\xf0\x9f\x88\x9e", "\xf0\x9f\x88\x9f", + "\xf0\x9f\x88\xa0", "\xf0\x9f\x88\xa1", "\xf0\x9f\x88\xa2", "\xf0\x9f\x88\xa3", "\xf0\x9f\x88\xa4", + /* 81- 84 */ + "\xf0\x9f\x88\xa5", "\xf0\x9f\x85\x8e", "\xe3\x8a\x99", "\xf0\x9f\x88\x80", + }, + { /* row 91 */ + /* 1- 10 */ + "\xe2\x9b\xa3", "\xe2\xad\x96", "\xe2\xad\x97", "\xe2\xad\x98", "\xe2\xad\x99", + "\xe2\x98\x93", "\xe3\x8a\x8b", "\xe3\x80\x92", "\xe2\x9b\xa8", "\xe3\x89\x86", + /* 11- 20 */ + "\xe3\x89\x85", "\xe2\x9b\xa9", "\xe0\xbf\x96", "\xe2\x9b\xaa", "\xe2\x9b\xab", + "\xe2\x9b\xac", "\xe2\x99\xa8", "\xe2\x9b\xad", "\xe2\x9b\xae", "\xe2\x9b\xaf", + /* 21- 30 */ + "\xe2\x9a\x93", "\xe2\x9c\x88", "\xe2\x9b\xb0", "\xe2\x9b\xb1", "\xe2\x9b\xb2", + "\xe2\x9b\xb3", "\xe2\x9b\xb4", "\xe2\x9b\xb5", "\xf0\x9f\x85\x97", "\xe2\x92\xb9", + /* 31- 40 */ + "\xe2\x93\x88", "\xe2\x9b\xb6", "\xf0\x9f\x85\x9f", "\xf0\x9f\x86\x8b", "\xf0\x9f\x86\x8d", + "\xf0\x9f\x86\x8c", "\xf0\x9f\x85\xb9", "\xe2\x9b\xb7", "\xe2\x9b\xb8", "\xe2\x9b\xb9", + /* 41- 49 */ + "\xe2\x9b\xba", "\xf0\x9f\x85\xbb", "\xe2\x98\x8e", "\xe2\x9b\xbb", "\xe2\x9b\xbc", + "\xe2\x9b\xbd", "\xe2\x9b\xbe", "\xf0\x9f\x85\xbc", "\xe2\x9b\xbf", + }, + { /* row 92 */ + /* 1- 10 */ + "\xe2\x9e\xa1", "\xe2\xac\x85", "\xe2\xac\x86", "\xe2\xac\x87", "\xe2\xac\xaf", + "\xe2\xac\xae", "\xe5\xb9\xb4", "\xe6\x9c\x88", "\xe6\x97\xa5", "\xe5\x86\x86", + /* 11- 20 */ + "\xe3\x8e\xa1", "\xe3\x8e\xa5", "\xe3\x8e\x9d", "\xe3\x8e\xa0", "\xe3\x8e\xa4", + "\xf0\x9f\x84\x80", "\xe2\x92\x88", "\xe2\x92\x89", "\xe2\x92\x8a", "\xe2\x92\x8b", + /* 21- 30 */ + "\xe2\x92\x8c", "\xe2\x92\x8d", "\xe2\x92\x8e", "\xe2\x92\x8f", "\xe2\x92\x90", + "\xe6\xb0\x8f", "\xe5\x89\xaf", "\xe5\x85\x83", "\xe6\x95\x85", "\xe5\x89\x8d", + /* 31- 40 */ + "\xe6\x96\xb0", "\xf0\x9f\x84\x81", "\xf0\x9f\x84\x82", "\xf0\x9f\x84\x83", "\xf0\x9f\x84\x84", + "\xf0\x9f\x84\x85", "\xf0\x9f\x84\x86", "\xf0\x9f\x84\x87", "\xf0\x9f\x84\x88", "\xf0\x9f\x84\x89", + /* 41- 50 */ + "\xf0\x9f\x84\x8a", "\xe3\x88\xb3", "\xe3\x88\xb6", "\xe3\x88\xb2", "\xe3\x88\xb1", + "\xe3\x88\xb9", "\xe3\x89\x84", "\xe2\x96\xb6", "\xe2\x97\x80", "\xe3\x80\x96", + /* 51- 60 */ + "\xe3\x80\x97", "\xe2\x9f\x90", "\xc2\xb2", "\xc2\xb3", "\xf0\x9f\x84\xad", + /* no unicode chars for musical score symbols (56...85) */ + "?", "?", "?", "?", "?", + /* 61- 70 */ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", + /* 71- 80 */ + "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", + /* 81- 90 */ + "?", "?", "?", "?", "?", + "\xf0\x9f\x84\xac", "\xf0\x9f\x84\xab", "\xe3\x89\x87", "\xf0\x9f\x86\x90", "\xf0\x9f\x88\xa6", + /* 91 */ + "\xe2\x84\xbb", + }, + { /* row 93 */ + /* 1- 10 */ + "\xe3\x88\xaa", "\xe3\x88\xab", "\xe3\x88\xac", "\xe3\x88\xad", "\xe3\x88\xae", + "\xe3\x88\xaf", "\xe3\x88\xb0", "\xe3\x88\xb7", "\xe3\x8d\xbe", "\xe3\x8d\xbd", + /* 11- 20 */ + "\xe3\x8d\xbc", "\xe3\x8d\xbb", "\xe2\x84\x96", "\xe2\x84\xa1", "\xe3\x80\xb6", + "\xe2\x9a\xbe", "\xf0\x9f\x89\x80", "\xf0\x9f\x89\x81", "\xf0\x9f\x89\x82", "\xf0\x9f\x89\x83", + /* 21- 30 */ + "\xf0\x9f\x89\x84", "\xf0\x9f\x89\x85", "\xf0\x9f\x89\x86", "\xf0\x9f\x89\x87", "\xf0\x9f\x89\x88", + "\xf0\x9f\x84\xaa", "\xf0\x9f\x88\xa7", "\xf0\x9f\x88\xa8", "\xf0\x9f\x88\xa9", "\xf0\x9f\x88\x94", + /* 31- 40 */ + "\xf0\x9f\x88\xaa", "\xf0\x9f\x88\xab", "\xf0\x9f\x88\xac", "\xf0\x9f\x88\xad", "\xf0\x9f\x88\xae", + "\xf0\x9f\x88\xaf", "\xf0\x9f\x88\xb0", "\xf0\x9f\x88\xb1", "\xe2\x84\x93", "\xe3\x8e\x8f", + /* 41- 50 */ + "\xe3\x8e\x90", "\xe3\x8f\x8a", "\xe3\x8e\x9e", "\xe3\x8e\xa2", "\xe3\x8d\xb1", + "", "", "\xc2\xbd", "\xe2\x86\x89", "\xe2\x85\x93", + /* 51- 60 */ + "\xe2\x85\x94", "\xc2\xbc", "\xc2\xbe", "\xe2\x85\x95", "\xe2\x85\x96", + "\xe2\x85\x97", "\xe2\x85\x98", "\xe2\x85\x99", "\xe2\x85\x9a", "\xe2\x85\x90", + /* 61- 70 */ + "\xe2\x85\x9b", "\xe2\x85\x91", "\xe2\x85\x92", "\xe2\x98\x80", "\xe2\x98\x81", + "\xe2\x98\x82", "\xe2\x9b\x84", "\xe2\x98\x96", "\xe2\x98\x97", "\xe2\x9b\x89", + /* 71- 80 */ + "\xe2\x9b\x8a", "\xe2\x99\xa6", "\xe2\x99\xa5", "\xe2\x99\xa3","\xe2\x99\xa0", + "\xe2\x9b\x8b", "\xe2\xa8\x80", "\xe2\x80\xbc", "\xe2\x81\x89", "\xe2\x9b\x85", + /* 81- 90 */ + "\xe2\x98\x94", "\xe2\x9b\x86", "\xe2\x98\x83", "\xe2\x9b\x87", "\xe2\x9a\xa1", + "\xe2\x9b\x87", "", "\xe2\x9a\x9e", "\xe2\x9a\x9f", "\xe2\x99\xac", + /* 91 */ + "\xe2\x98\x8e", + }, + { /* row 94 */ + /* 1- 10 */ + "\xe2\x85\xa0", "\xe2\x85\xa1", "\xe2\x85\xa2", "\xe2\x85\xa3", "\xe2\x85\xa4", + "\xe2\x85\xa5", "\xe2\x85\xa6", "\xe2\x85\xa7", "\xe2\x85\xa8", "\xe2\x85\xa9", + /* 11- 20 */ + "\xe2\x85\xaa", "\xe2\x85\xab", "\xe2\x91\xb0", "\xe2\x91\xb1", "\xe2\x91\xb2", + "\xe2\x91\xb3", "\xe2\x91\xb4", "\xe2\x91\xb5", "\xe2\x91\xb6", "\xe2\x91\xb7", + /* 21- 30 */ + "\xe2\x91\xb8", "\xe2\x91\xb9", "\xe2\x91\xba", "\xe2\x91\xbb", "\xe2\x91\xbc", + "\xe2\x91\xbd", "\xe2\x91\xbe", "\xe2\x91\xbf", "\xe3\x89\x91", "\xe3\x89\x92", + /* 31- 40 */ + "\xe3\x89\x93", "\xe3\x89\x94", "\xf0\x9f\x84\x90", "\xf0\x9f\x84\x91", "\xf0\x9f\x84\x92", + "\xf0\x9f\x84\x93", "\xf0\x9f\x84\x94", "\xf0\x9f\x84\x95", "\xf0\x9f\x84\x96", "\xf0\x9f\x84\x97", + /* 41- 50 */ + "\xf0\x9f\x84\x98", "\xf0\x9f\x84\x99", "\xf0\x9f\x84\x9a", "\xf0\x9f\x84\x9b", "\xf0\x9f\x84\x9c", + "\xf0\x9f\x84\x9d", "\xf0\x9f\x84\x9e", "\xf0\x9f\x84\x9f", "\xf0\x9f\x84\xa0", "\xf0\x9f\x84\xa1", + /* 51- 60 */ + "\xf0\x9f\x84\xa2", "\xf0\x9f\x84\xa3", "\xf0\x9f\x84\xa4", "\xf0\x9f\x84\xa5", "\xf0\x9f\x84\xa6", + "\xf0\x9f\x84\xa7", "\xf0\x9f\x84\xa8", "\xf0\x9f\x84\xa9", "\xe3\x89\x95", "\xe3\x89\x96", + /* 61- 70 */ + "\xe3\x89\x97", "\xe3\x89\x98", "\xe3\x89\x99", "\xe3\x89\x9a", "\xe2\x91\xa0", + "\xe2\x91\xa1", "\xe2\x91\xa2", "\xe2\x91\xa3", "\xe2\x91\xa4", "\xe2\x91\xa5", + /* 71- 80 */ + "\xe2\x91\xa6", "\xe2\x91\xa7", "\xe2\x91\xa8", "\xe2\x91\xa9", "\xe2\x91\xaa", + "\xe2\x91\xab", "\xe2\x91\xac", "\xe2\x91\xad", "\xe2\x91\xae", "\xe2\x91\xaf", + /* 81- 90 */ + "\xe2\x9d\xb6", "\xe2\x9d\xb7", "\xe2\x9d\xb8", "\xe2\x9d\xb9", "\xe2\x9d\xba", + "\xe2\x9d\xbb", "\xe2\x9d\xbc", "\xe2\x9d\xbd", "\xe2\x9d\xbe", "\xe2\x9d\xbf", + /* 91- 93 */ + "\xe2\x93\xab", "\xe2\x93\xac", "\xe3\x89\x9b", + } +}; + +static unsigned char append_arib_char (ISDBSubContext *ctx, uint8_t c1, uint8_t c2) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + struct b24str_state *ts = &ctx->current_state.text_state; + int gidx; + uint8_t code; + size_t ilen, olen, ret; + char indata[3], *obuf, *ibuf; + const uint8_t *p; + uint8_t *q; + int len; + + ibuf = indata; + // make room for a new char. (at least 6 + 1 bytes) + reserve_buf(ctx, 7); + + obuf = ctx->text.buf + ctx->text.used; + olen = ctx->text.len - ctx->text.used; + + if (ts->ss > 1) + gidx = ts->ss; + else if (c1 & 0x80) + gidx = ts->gr; + else + gidx = ts->gl; + code = ts->g[gidx].code; + + // some hacks + if ((c1 & 0x7F) == 0x20 || (c1 & 0x7F) == 0x7F) // SP or DEL + code = CODE_ASCII; + if (code == CODE_JISX0208 && (c1 & 0x7F) >= 0x75) + code = CODE_EXT; + + switch (code) { + case CODE_ASCII: + case CODE_ASCII2: + ls->in_combining = 0; + c1 &= 0x7F; + if (c1 != 0x7F) + ctx->text.buf[ctx->text.used++] = c1; // no conversion needed + else { + // replace DELL with black-square (U+25A0) + memcpy(ctx->text.buf + ctx->text.used, "\xe2\x96\xa0", 3); + ctx->text.used += 3; + } + break; + + case CODE_JISX0208: + case CODE_JISX0213_1: + c1 |= 0x80; + c2 |= 0x80; + // non-spacing char? + if ((c1 == 0xA1 && c2 >= 0xAD && c2 <= 0xB2) || + (c1 == 0xA2 && c2 == 0xFE)) { + ls->in_combining = 1; + // U+20DD (0xe2839d) combining enclosing circle + p = (c1 == 0xA1) ? combining1[c2 - 0xAD] : + (const uint8_t *) "\xe2\x83\x9d"; + len = strlen(p); + memcpy(ctx->text.buf + ctx->text.used, p, len); + ctx->text.used += len; + break; + } + ls->in_combining = 0; + ibuf[0] = c1; + ibuf[1] = c2;// ibuf[]: EUC-JP2013 kanji + ilen = 2; + ret = iconv(ctx->iconv, &ibuf, &ilen, &obuf, &olen); + if (ret != (size_t) -1) + ctx->text.used += obuf - &ctx->text.buf[ctx->text.used]; + else + mp_msg(MSGT_DECSUB, MSGL_INFO, + "bad data as EUC-JP(%d). sp:%lu 0x%02hhx 0x%02hhx\n", + errno, olen, c1, c2); + break; + case CODE_JISX0213_2: + ls->in_combining = 0; + c1 |= 0x80; + c2 |= 0x80; + ilen = 3; + ibuf[0] = '\x8f'; + ibuf[0] = c1; + ibuf[1] = c2; + ret = iconv(ctx->iconv, &ibuf, &ilen, &obuf, &olen); + if (ret != (size_t) -1) + ctx->text.used += obuf - &ctx->text.buf[ctx->text.used]; + else + mp_msg(MSGT_DECSUB, MSGL_INFO, + "bad data as EUC-JP. 0x8f 0x%02hhx 0x%02hhx\n", c1, c2); + break; + + case CODE_JISX0201_KATA: + ls->in_combining = 0; + c1 &= 0x7F; + // map to U+FF60 + (c1 - 0x20) = U+FF00 + (c1 + 0x40) + c1 += 0x40; + q = ctx->text.buf + ctx->text.used; + q[0] = 0xef; + q[1] = 0xbc + (c1 >> 6); + q[2] = 0x80 + (c1 & 0x3F); + ctx->text.used += 3; + break; + + case CODE_X_HIRA: + case CODE_X_HIRA_P: + case CODE_X_KATA: + case CODE_X_KATA_P: + ls->in_combining = 0; + c1 &= 0x7F; + if (c1 < 0x77) { + q = ctx->text.buf + ctx->text.used; + // hira: {U+3040+ (c1 - 0x20)} -> {U+3000 + (c1 + 0x20)} + // kata: {U+30A0 + (c1 - 0x20)} -> {U+3000 + (c1 + 0x80)} + c1 += (code == CODE_X_HIRA || code == CODE_X_HIRA_P) ? 0x20 : 0x80; + q[0] = 0xe3; + q[1] = 0x80 + (c1 >> 6); + q[2] = 0x80 + (c1 & 0x3F); + ctx->text.used += 3; + } else { + c1 -= 0x77; + p = (code == CODE_X_HIRA || code == CODE_X_HIRA_P) ? + hira_symbols[c1] : kata_symbols[c1]; + len = strlen(p); + memcpy(ctx->text.buf + ctx->text.used, p, len); + ctx->text.used += len; + } + break; + + case CODE_EXT: // symbols defined in row 85.86, 90..94 + ls->in_combining = 0; + c1 &= 0x7F; + c2 &= 0x7F; + switch (c1) { + case 0x75: // row 85-86 + case 0x76: + c2 -= 0x21; + if (c1 == 0x76) + c2 += 94; + if (c2 < 137) { + p = trans_ext85[c2]; + len = strlen(p); + memcpy(ctx->text.buf + ctx->text.used, p, len); + ctx->text.used += len; + } else + mp_msg(MSGT_DECSUB, MSGL_INFO, "bad data as extra char.\n"); + break; + case 0x7A: // row 90-94 + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + c1 -= 0x7A; + c2 -= 0x21; + p = trans_ext90[c1][c2]; + len = strlen(p); + if (len > 0) { + memcpy(ctx->text.buf + ctx->text.used, p, len); + ctx->text.used += len; + } else + mp_msg(MSGT_DECSUB, MSGL_INFO, "bad data as extra char.\n"); + break; + + default: + mp_msg(MSGT_DECSUB, MSGL_V, + "un-supported data 0x%02hhx 0x%02hhx.\n", c1, c2); + } + break; + + // non-spacing mosaic chars... just skip. + // don't touch in_combining, as other non-spacing char's may follow/precede... + case CODE_MOSAIC_C: + case CODE_MOSAIC_D: + break; + + case CODE_X_MACRO: + break; // processed later + + default: + ls->in_combining = 0; + // replace this unknown char with U+FFFD + memcpy(ctx->text.buf + ctx->text.used, "\xef\xbf\xbd", 3); + ctx->text.used += 3; + mp_msg(MSGT_DECSUB, MSGL_V, + "unknown charset:0x%02hhx data 0x%02hhx 0x%02hhx.\n", code, c1, c2); + break; + } + ctx->text.buf[ctx->text.used] = '\0'; + return code; +} + +static void insert_str(ISDBSubContext *ctx, const char *txt, int begin) +{ + int end = ctx->text.used; + size_t len = strlen(txt); + + if (len == 0 || len > 128) + return; + + reserve_buf(ctx, len + 1); // +1 for terminating '\0' + memmove(ctx->text.buf + begin + len, ctx->text.buf + begin, end - begin); + memcpy(ctx->text.buf + begin, txt, len); + ctx->text.txt_tail += len; + ctx->text.used += len; + ctx->text.buf[ctx->text.used] = '\0'; +} + +static void advance(ISDBSubContext *ctx); + +static void fixup_linesep(ISDBSubContext *ctx) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + char tmp[16]; + int lsp; + + if (ls->prev_break_idx <= 0) + return; + // adjust baseline if all chars in the line are 50% tall of one font size. + if (ls->shift_baseline && IS_HORIZONTAL_LAYOUT(ls->format)) { + int delta = ls->cell_spacing.row / 4; + ls->linesep_upper += delta; + ls->line_desc -= delta; + mp_msg(MSGT_DECSUB, MSGL_V, "baseline shifted down %dpx.\n", delta); + } + + // not the first line + tmp[0]='\0'; + lsp = ls->prev_line_desc + ls->linesep_upper; + av_strlcatf(tmp, sizeof(tmp), "{\\lsp%d}", lsp); + insert_str(ctx, tmp, ls->prev_break_idx); +} + +static void do_line_break(ISDBSubContext *ctx) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + int csp; + char tmp[32]; + + if (IS_HORIZONTAL_LAYOUT(ls->format)) + csp = ls->cell_spacing.col * ls->font_scale.fscx / 100; + else + csp = ls->cell_spacing.col * ls->font_scale.fscy / 100; + + if (ls->line_width == 0) { + if (ls->prev_line_bottom == 0) { + if (IS_HORIZONTAL_LAYOUT(ls->format)) + ls->block_offset_h = csp / 2; + else + ls->block_offset_v = csp / 2; + } + + // avoid empty lines by prepending a space, + // as ASS halves line-spacing of empty lines. + append_str(ctx, "\xe3\x80\x80"); + advance(ctx); + } + fixup_linesep(ctx); + + ls->prev_break_idx = ctx->text.used; + tmp[0] = '\0'; + av_strlcatf(tmp, sizeof(tmp), "\\N{\\lsp0\\fsp%d}", ls->cell_spacing.col); + append_str(ctx, tmp); + ls->prev_line_desc = ls->line_desc; + ls->prev_line_bottom += ls->linesep_upper + ls->line_height + ls->line_desc; + ls->prev_char_sep = csp; + ls->line_height = 0; + ls->line_width = 0; + ls->line_desc = 0; + ls->linesep_upper = 0; + ls->shift_baseline = 0; +} + +// a new char will be appended. check h-spacing and line-breaking. +static void pre_advance(ISDBSubContext *ctx) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + int csp, cscale; + int w; + int blocklen; + int delta; + + if (IS_HORIZONTAL_LAYOUT(ls->format)) { + cscale = ls->font_scale.fscx; + w = ls->font_size * cscale / 100; + csp = ls->cell_spacing.col * cscale / 100; + if (ls->prev_line_bottom == 0 && ls->line_width == 0) + ls->block_offset_h = csp / 2; + blocklen = ls->display_area.w - ls->block_offset_h; + } else { + cscale = ls->font_scale.fscy; + w = ls->font_size * cscale / 100; + csp = ls->cell_spacing.col * cscale / 100; + if (ls->prev_line_bottom == 0 && ls->line_width == 0) + ls->block_offset_v = csp / 2; + blocklen = ls->display_area.h - ls->block_offset_v; + } + if (csp != ls->prev_char_sep) + delta = ((ls->prev_char_sep + 1) / 2 + csp / 2) - ls->prev_char_sep; + else + delta = 0; + + // check line break; + // note: at the head of a line, line breaking is useless. + if (ls->line_width != 0 && + ls->line_width + delta + w > blocklen) { + mp_msg(MSGT_DECSUB, MSGL_V, "auto line break at %lu.\n", + ctx->text.used); + do_line_break(ctx); + } else if (ls->line_width != 0 && delta != 0) { + char tmp[32]; + + // need to compensate the wrong charsep of the previously added char. + // hack: insert zero-width space with the compensating \fsp. (can be <0) + // note that \fsp is scaled by \fscx in ASS. + tmp[0] = '\0'; + av_strlcatf(tmp, sizeof(tmp), "{\\fsp%d}\xe2\x80\x8b{\\fsp%d}", + delta * 100 / cscale, ls->cell_spacing.col); + append_str(ctx, tmp); + ls->line_width += delta; + } +} + +static void advance(ISDBSubContext *ctx) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + int cscale; + int h; + int asc, desc; + int csp; + + if (IS_HORIZONTAL_LAYOUT(ls->format)) { + cscale = ls->font_scale.fscx; + h = ls->font_size * ls->font_scale.fscy / 100; + if (ls->font_scale.fscy == 200) { + desc = ls->cell_spacing.row / 2; + asc = ls->cell_spacing.row * 2 - desc + h; + } else { + desc = ls->cell_spacing.row * ls->font_scale.fscy / 200; + asc = ls->cell_spacing.row * ls->font_scale.fscy / 100 - desc + h; + } + if (asc > ls->line_height + ls->linesep_upper) { + if (h > ls->line_height) + ls->line_height = h; + ls->linesep_upper = asc - ls->line_height; + } else if (h > ls->line_height) { + ls->linesep_upper = ls->line_height + ls->linesep_upper - h; + ls->line_height = h; + } + + if (ls->prev_line_bottom == 0 && ls->linesep_upper > ls->block_offset_v) + ls->block_offset_v = ls->linesep_upper; + if (ls->font_scale.fscy != 50) + ls->shift_baseline = 0; + } else { + int lsp; + cscale = ls->font_scale.fscy; + h = ls->font_size * ls->font_scale.fscx / 100; + lsp = ls->cell_spacing.row * ls->font_scale.fscx / 100; + desc = h / 2 + lsp / 2; + asc = h - h / 2 + lsp - lsp / 2; + if (asc > ls->line_height + ls->linesep_upper) { + if (h - h / 2 > ls->line_height) + ls->line_height = h - h / 2; + ls->linesep_upper = asc - ls->line_height; + } else if (h - h / 2 > ls->line_height) { + ls->linesep_upper = ls->line_height + ls->linesep_upper - h + h / 2; + ls->line_height = h - h / 2; + } + + if (ls->prev_line_bottom == 0 && ls->linesep_upper > ls->block_offset_h) + ls->block_offset_h = ls->linesep_upper; + ls->shift_baseline = 0; + } + if (desc > ls->line_desc) + ls->line_desc = desc; + + csp = ls->cell_spacing.col * cscale / 100; + ls->line_width += ls->font_size * cscale / 100 + csp; + ls->prev_char_sep = csp; +} + +static void forward_position(ISDBSubContext *ctx, int rows, int cols) +{ + int i; + + for (i = 0; i < rows; i++) + do_line_break(ctx); + for (i = 0; i < cols; i++) { + pre_advance(ctx); + append_str(ctx, "\xe3\x80\x80"); + advance(ctx); + } + if (rows > 0 && cols > 0) + ctx->current_state.layout_state.shift_baseline = 1; +} + +static const uint8_t *proc_char(ISDBSubContext *ctx, + const uint8_t *buf, int buf_size) +{ + struct isdbsub_layout *ls = &ctx->current_state.layout_state; + struct b24str_state *ts = &ctx->current_state.text_state; + unsigned int begin, end; + char *ch = NULL; + + begin = end = ctx->text.used; + + while (buf_size > 0 && (buf[0] & 0x60) != 0) { + uint8_t c1, c2 = 0; + unsigned char ctype; + + c1 = buf[0]; + buf ++; + buf_size --; + + if ((c1 & 0x7F) != 0x20 && (c1 & 0x7F) != 0x7F) { // not (SP or DEL) + // multi-byte? + if ((ts->ss > 0 && ts->g[ts->ss].mb > 1) + || (!ts->ss && (c1 & 0x80) && ts->g[ts->gr].mb > 1) + || (!ts->ss && !(c1 & 0x80) && ts->g[ts->gl].mb > 1)) { + if (buf_size < 1) + return NULL; + c2 = buf[0]; + buf ++; + buf_size --; + } + } + + if (!ls->in_combining) { + pre_advance(ctx); + begin = end = ctx->text.used; + } + ctype = append_arib_char(ctx, c1, c2); + ts->ss = 0; + if (ctype == 0) + continue; + if (ctype == CODE_X_MACRO) { + mp_msg(MSGT_DECSUB, MSGL_DBG2, "macro 0x%02hhx.\n", c1); + if ((c1 & 0x70) == 0x60) { + c1 &= 0x0F; + proc_ctl(ctx, Default_macro[c1], strlen(Default_macro[c1])); + } + continue; + } + + // if non-spacing sequence has terminated,.. + if (!ls->in_combining && end - begin > 0 && ctx->text.used > end) { + char tmp[8]; // UTF-8 char is 6bytes at maximum.... + unsigned int len; + + len = ctx->text.used - end; + if (len > sizeof(tmp)) // for safety. + len = sizeof(tmp); + memcpy(tmp, ctx->text.buf + end, len); + memmove(ctx->text.buf + begin + len, ctx->text.buf + begin, end - begin); + memcpy(ctx->text.buf + begin, tmp, len); + mp_msg(MSGT_DECSUB, MSGL_V, + "moved the terminating spacing char to the head(%d).\n", begin); + } + end = ctx->text.used; + if (ls->in_combining) + continue; + + // ls->in_combining == 0 + advance(ctx); + + // repeat + if (ls->repeat_count >= 0) { + // save the repeating char first. + ch = calloc(1, end - begin + 1); + if (ch) + memcpy(ch, ctx->text.buf + begin, end - begin); + else { + mp_msg(MSGT_DECSUB, MSGL_WARN, + "out of memory for repeating char.\n"); + ls->repeat_count = -1; + } + } + + if (ls->repeat_count == 0) { + pre_advance(ctx); + while (ls->line_width != 0) { + append_str(ctx, ch); + advance(ctx); + // prepare for the next one. + pre_advance(ctx); + } + } else if (ls->repeat_count > 0) { + while (--ls->repeat_count > 0) { + pre_advance(ctx); + append_str(ctx, ch); + advance(ctx); + } + } + if (ls->repeat_count > -1) + free(ch); + + ls->repeat_count = -1; + } + if (buf_size <= 0) + return NULL; + // buf_size > 0 => assert((buf[0] & 0x60) == 0); + return buf; +} + +static void process_txt_du(ISDBSubContext *ctx, const uint8_t *buf, + int buf_size) +{ + const char *p, *q; + + ctx->current_state = ctx->default_states[ctx->lang_tag]; + reset_state(&ctx->current_state); + iconv(ctx->iconv, NULL, NULL, NULL, NULL); + + p = buf; + while (buf_size > 0) { + q = proc_ctl(ctx, p, buf_size); // update ctx->current_state + if (!q) + break; + buf_size -= q - p; + p = q; + q = proc_char(ctx, p, buf_size); // append to ctx->text + ctx->text.txt_tail = ctx->text.used; + if (!q) + break; + buf_size -= q - p; + p = q; + if (ctx->current_state.need_init) + setup_line_head(ctx); + } +} + +static void process_mngmnt_dg(ISDBSubContext *ctx, const uint8_t *buf, + int buf_size) +{ + const uint8_t *p = buf; + int lang_num; + int du_loop_len; + int i; + + ctx->timing_mode = *(p++) >> 6; + if (ctx->timing_mode == ISDBSUB_TMD_OFFSET) { + ctx->offset.hour = (p[0] >> 4) * 10 + (p[0] & 0x0f); + ctx->offset.min = (p[1] >> 4) * 10 + (p[1] & 0x0f); + ctx->offset.sec = (p[2] >> 4) * 10 + (p[2] & 0x0f); + ctx->offset.ms = (p[3] >> 4) * 100 + (p[3] & 0x0f) * 10 + (p[4] >> 4); + p += 5; + } + lang_num = *(p++); + for (i = 0; i < lang_num; i++) { + int lang_tag = *p >> 5; + int disp_mode = *p & 0x0f; + + p++; + if ((disp_mode & 0x0c) == 0x0c) + p++; // skip display condition + p += 3; // skip lang code + if (lang_tag < ISDBSUB_MAX_LANG) { + ISDBSubState *state = &ctx->default_states[lang_tag]; + int format; + state->auto_display = disp_mode >> 3; + state->rollup_mode = (*p & 0x03); + format = *p >> 4; + state->layout_state.is_profile_c = (format == 0x0F); + if (state->layout_state.is_profile_c) + format = ISDBSUB_FMT_960H; + if (format < ISDBSUB_FMT_960H || format > ISDBSUB_FMT_720V) { + mp_msg(MSGT_DECSUB, MSGL_INFO, + "illegal format:0x%02x in sub. mngment data.\n", format); + format = ISDBSUB_FMT_960H; // fallback + } + state->layout_state.format = format; + if ((*p & 0x0c) != 0x00) + mp_msg(MSGT_DECSUB, MSGL_WARN, + "char encoding:%d not supported.\n", ((*p & 0x0c) >> 2)); + init_layout(&state->layout_state); + reset_state(state); + clear_text(ctx); + } + p++; + } + ctx->current_state = ctx->default_states[ctx->lang_tag]; + set_format(ctx); + + du_loop_len = AV_RB24(p); + p += 3; + if (p + du_loop_len > buf + buf_size) + du_loop_len = buf + buf_size - p; + while (du_loop_len >= 5) { + int du_size = AV_RB24(p + 2); + + if (p[0] != ISDBSUB_UNIT_SEP || du_loop_len < 5 + du_size) + break; + du_loop_len -= (5 + du_size); + + // ignore DRCS. only txt data units. + if (p[1] == ISDBSUB_DU_TYPE_TXT) { + memdump(MSGL_DBG2, p + 5, du_size); + process_txt_du(ctx, p + 5, du_size); // control seq. only + // copy back SWF, SDP, SDF,... + ctx->default_states[ctx->lang_tag] = ctx->current_state; + } + p += 5 + du_size; + } +} + +static void process_sub_dg(ISDBSubContext *ctx, const uint8_t *buf, + int buf_size) +{ + const uint8_t *p = buf; + int du_loop_len; + + if (ctx->pts == AV_NOPTS_VALUE) { + mp_msg(MSGT_DECSUB, MSGL_INFO, "no timestamp on subtitle text data."); + return; + } + clear_text(ctx); + setup_line_head(ctx); + + ctx->timing_mode = *(p++) >> 6; + // subtitle data should be TMD_FREE (ARIB TR-B15). + + if (ctx->timing_mode != ISDBSUB_TMD_FREE) { + ctx->start.hour = (p[0] >> 4) * 10 + (p[0] & 0x0f); + ctx->start.min = (p[1] >> 4) * 10 + (p[1] & 0x0f); + ctx->start.sec = (p[2] >> 4) * 10 + (p[2] & 0x0f); + ctx->start.ms = (p[3] >> 4) * 100 + (p[3] & 0x0f) * 10 + (p[4] >> 4); + if (ctx->timing_mode == ISDBSUB_TMD_OFFSET) { + ctx->start.ms += ctx->offset.ms; + if (ctx->start.ms >= 1000) { + ctx->start.ms -= 1000; + ctx->start.sec ++; + } + ctx->start.sec += ctx->offset.sec; + if (ctx->start.sec >= 60) { + ctx->start.sec -= 60; + ctx->start.min ++; + } + ctx->start.min += ctx->offset.min; + if (ctx->start.min >= 60) { + ctx->start.min -= 60; + ctx->start.hour ++; + } + ctx->start.hour += ctx->offset.hour; + } + p += 5; + } + + du_loop_len = AV_RB24(p); + p += 3; + if (p + du_loop_len > buf + buf_size) + du_loop_len = buf + buf_size - p; + while (du_loop_len >= 5) { + int du_size = AV_RB24(p + 2); + + if (p[0] != ISDBSUB_UNIT_SEP || du_loop_len < 5 + du_size) + break; + du_loop_len -= (5 + du_size); + + // ignore DRCS. only txt data units. + if (p[1] == ISDBSUB_DU_TYPE_TXT) { + memdump(MSGL_DBG2, p + 5, du_size); + process_txt_du(ctx, p + 5, du_size); + } + p += 5 + du_size; + } + append_event(ctx); +} + + +static int do_init(ISDBSubContext *ctx) +{ + int i; + + for (i = 65; i < 73; i++) + Default_clut[i] = Default_clut[i - 65] & ~RGBA(0,0,0,128); + for (i = 73; i < 128; i++) + Default_clut[i] = Default_clut[i - 64] & ~RGBA(0,0,0,128); + + Crc_table = av_crc_get_table(AV_CRC_16_CCITT); + + ctx->last_mngmnt_id = ISDBSUB_NO_DGID; + ctx->last_mngmnt_pts = AV_NOPTS_VALUE; + ctx->iconv = iconv_open("UTF-8", "EUC-JISX0213"); + if (ctx->iconv == (iconv_t) -1) + return -1; + return 0; +} + +void isdbsub_reset(struct sh_sub *sh) +{ + ISDBSubContext *ctx = sh->context; + + if (!ctx) + return; + + if (ctx->iconv != (iconv_t) -1) + iconv_close(ctx->iconv); + + free(ctx->text.buf); + free(ctx->script_info); + free(ctx->events); + + free(ctx); + sh->context = NULL; +} + +/** + * Decode a ISDB subtitle packet. + * \return < 0 on error, 'a' if further processing is needed + */ +int isdbsub_decode(struct sh_sub *sh, uint8_t **data, int *size, + double *pts, double *endpts) +{ + ISDBSubContext *ctx = sh->context; + const uint8_t *buf = *data; + int buf_size = *size; + int64_t orig_pts; + const uint8_t *p, *p_end; + + mp_msg(MSGT_DECSUB, MSGL_V, "ISDB sub packet:\n"); + memdump(MSGL_DBG2, buf, buf_size); + + ass_font_scale = 1.; + if (!ctx) { + ctx = sh->context = calloc(1, sizeof(ISDBSubContext)); + if (!sh->context || do_init(sh->context)) { + mp_msg(MSGT_DECSUB, MSGL_WARN, + "Could not initialize codec context.\n"); + return -1; + } + } + + if (buf_size <= 10 || buf[0] != ISDBSUB_DATA_ID || buf[1] != 0xff) { + mp_msg(MSGT_DECSUB, MSGL_INFO, "incomplete or broken packet\n"); + return -1; + } + /* set default output */ + *data = NULL; + *size = 0; + + orig_pts = (*pts != MP_NOPTS_VALUE) ? *pts * 1000 : AV_NOPTS_VALUE; + ctx->pts = orig_pts; + ctx->duration = 5 * 1000; //temporary. + ctx->lang_tag = sh->cur_lang_tag; + + if (orig_pts != AV_NOPTS_VALUE && ctx->last_mngmnt_pts != AV_NOPTS_VALUE) { + int64_t t = orig_pts; + char t1[16], t2[16]; + + // check pts timeout, but taking PTS wrap-around into account. + if (t < ctx->last_mngmnt_pts && + (ctx->last_mngmnt_pts - t) > (MPEGTS_MAX_PTS >> 1)) { + mp_msg(MSGT_DECSUB, MSGL_INFO, "PTS wrap-around %s -> %s\n", + pts_to_str(ctx->last_mngmnt_pts, t1), pts_to_str(t, t2)); + t += MPEGTS_MAX_PTS; + } + if (t < ctx->last_mngmnt_pts || + t - ctx->last_mngmnt_pts > ISDBSUB_MGMNT_TIMEOUT) { + mp_msg(MSGT_DECSUB, MSGL_INFO, + "Subtitle Management DataGroup time-out. %s -> %s\n", + pts_to_str(ctx->last_mngmnt_pts, t1), pts_to_str(t, t2)); + ctx->last_mngmnt_pts = AV_NOPTS_VALUE; + } + } + + p = buf + 3 + (buf[2] & 0x0f); + p_end = buf + buf_size; + + while (p_end - p >= 7) { + int dg_id = p[0] >> 2; + int dg_size = AV_RB16(p + 3); + uint32_t crc = 0; + + if (p + 5 + dg_size + 2 > p_end || + (crc = av_crc(Crc_table, 0, p, 5 + dg_size + 2)) != 0) { + mp_msg(MSGT_DECSUB, MSGL_INFO, "incomplete or broken packet. " + "ofs:%ld l:%d crc:0x%04hx\n", p - buf, 5 + dg_size + 2, crc); + return -1; + } + p += 5; + + if ((dg_id & 0x0f) == 0) { // subtile management data group + char t1[16]; + + if (dg_id != ctx->last_mngmnt_id + || ctx->last_mngmnt_pts == AV_NOPTS_VALUE) + process_mngmnt_dg(ctx, p, dg_size); + else + mp_msg(MSGT_DECSUB, MSGL_DBG2, + "Skip the same subtitle management data group.\n"); + + ctx->last_mngmnt_id = dg_id; + ctx->last_mngmnt_pts = orig_pts; + mp_msg(MSGT_DECSUB, MSGL_DBG2, + "last_mngmnt_pts set to %s\n", pts_to_str(orig_pts, t1)); + } else if ((dg_id & 0x0f) == ctx->lang_tag + 1) { // subtile data group + if (ctx->last_mngmnt_id == ISDBSUB_NO_DGID) + mp_msg(MSGT_DECSUB, MSGL_V, "no management data group received yet.\n"); + else if ((dg_id & 0xf0) == ctx->last_mngmnt_id) + process_sub_dg(ctx, p, dg_size); + } else + mp_msg(MSGT_DECSUB, MSGL_DBG2, + "Subtitle data group id 0x%02x, length %d\n", dg_id, dg_size); + + p += dg_size + 2; + } + + if (orig_pts != AV_NOPTS_VALUE && ctx->last_mngmnt_pts == AV_NOPTS_VALUE) { + ctx->last_mngmnt_pts = orig_pts; + mp_msg(MSGT_DECSUB, MSGL_V, + "replaced empty last_mngmnt_pts with the next received PTS.\n"); + } + + do_output(ctx, data, size); + if (endpts && orig_pts != AV_NOPTS_VALUE) + *endpts = (ctx->pts + ctx->duration) / 1000.0; + return *data ? 'a' : 0; +} diff --git a/sub/isdbsubdec.h b/sub/isdbsubdec.h new file mode 100644 index 000000000..4a6dd030a --- /dev/null +++ b/sub/isdbsubdec.h @@ -0,0 +1,30 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_ISDBSUBDEC_H +#define MPLAYER_ISDBSUBDEC_H + +#include + +struct sh_sub; + +void isdbsub_reset(struct sh_sub *sh); +int isdbsub_decode(struct sh_sub *sh, uint8_t **data, int *size, + double *pts, double *endpts); + +#endif /* MPLAYER_ISDBSUBDEC_H */ diff --git a/sub/subreader.c b/sub/subreader.c index db8534921..15c12a612 100644 --- a/sub/subreader.c +++ b/sub/subreader.c @@ -2505,6 +2505,7 @@ void sub_free( sub_data * subd ) * <> and {} are interpreted as comment delimiters, "\n", "\N", '\n', '\r' * and '\0' are interpreted as newlines, duplicate, leading and trailing * newlines are ignored. + * plus, leading and traing [zero-width] spaces are ignored. */ void sub_add_text(subtitle *sub, const char *txt, int len, double endpts, int strip_markup) { int comment = 0; @@ -2543,6 +2544,13 @@ void sub_add_text(subtitle *sub, const char *txt, int len, double endpts, int st i--; c = 0; } + if (c == ' ') + continue; + // strip [zero-width] space + if (i + 2 < len && c == '\xe2' && txt[i+1] == '\x80' && txt[i+2] == '\x8b') { + i += 2; + continue; + } if (c == '\\' && i + 1 < len) { c = txt[++i]; if (c == 'n' || c == 'N') c = 0;