diff options
Diffstat (limited to 'libhb')
-rw-r--r-- | libhb/common.c | 30 | ||||
-rw-r--r-- | libhb/common.h | 7 | ||||
-rw-r--r-- | libhb/decssasub.c | 284 | ||||
-rw-r--r-- | libhb/hb_json.c | 57 | ||||
-rw-r--r-- | libhb/muxavformat.c | 3 | ||||
-rw-r--r-- | libhb/reader.c | 4 | ||||
-rw-r--r-- | libhb/rendersub.c | 9 | ||||
-rw-r--r-- | libhb/stream.c | 3 | ||||
-rw-r--r-- | libhb/work.c | 8 |
9 files changed, 358 insertions, 47 deletions
diff --git a/libhb/common.c b/libhb/common.c index 525bf3ae4..f6b4ff7f1 100644 --- a/libhb/common.c +++ b/libhb/common.c @@ -4857,9 +4857,9 @@ int hb_subtitle_add(const hb_job_t * job, const hb_subtitle_config_t * subtitlec return 1; } -int hb_srt_add( const hb_job_t * job, +int hb_import_subtitle_add( const hb_job_t * job, const hb_subtitle_config_t * subtitlecfg, - const char *lang_code ) + const char *lang_code, int source ) { hb_subtitle_t *subtitle; iso639_lang_t *lang = NULL; @@ -4873,8 +4873,8 @@ int hb_srt_add( const hb_job_t * job, subtitle->id = (hb_list_count(job->list_subtitle) << 8) | 0xFF; subtitle->format = TEXTSUB; - subtitle->source = SRTSUB; - subtitle->codec = WORK_DECSRTSUB; + subtitle->source = source; + subtitle->codec = source == IMPORTSRT ? WORK_DECSRTSUB : WORK_DECSSASUB; subtitle->timebase.num = 1; subtitle->timebase.den = 90000; @@ -4895,6 +4895,13 @@ int hb_srt_add( const hb_job_t * job, return 1; } +int hb_srt_add( const hb_job_t * job, + const hb_subtitle_config_t * subtitlecfg, + const char *lang_code ) +{ + return hb_import_subtitle_add(job, subtitlecfg, lang_code, IMPORTSRT); +} + int hb_subtitle_can_force( int source ) { return source == VOBSUB || source == PGSSUB; @@ -4902,9 +4909,9 @@ int hb_subtitle_can_force( int source ) int hb_subtitle_can_burn( int source ) { - return source == VOBSUB || source == PGSSUB || source == SSASUB || - source == SRTSUB || source == CC608SUB || source == UTF8SUB || - source == TX3GSUB; + return source == VOBSUB || source == PGSSUB || source == SSASUB || + source == CC608SUB || source == UTF8SUB || source == TX3GSUB || + source == IMPORTSRT || source == IMPORTSSA; } int hb_subtitle_can_pass( int source, int mux ) @@ -4917,11 +4924,12 @@ int hb_subtitle_can_pass( int source, int mux ) case PGSSUB: case VOBSUB: case SSASUB: - case SRTSUB: case UTF8SUB: case TX3GSUB: case CC608SUB: case CC708SUB: + case IMPORTSRT: + case IMPORTSSA: return 1; default: @@ -4933,11 +4941,12 @@ int hb_subtitle_can_pass( int source, int mux ) { case VOBSUB: case SSASUB: - case SRTSUB: case UTF8SUB: case TX3GSUB: case CC608SUB: case CC708SUB: + case IMPORTSRT: + case IMPORTSSA: return 1; default: @@ -5446,7 +5455,7 @@ const char * hb_subsource_name( int source ) { case VOBSUB: return "VOBSUB"; - case SRTSUB: + case IMPORTSRT: return "SRT"; case CC608SUB: return "CC608"; @@ -5456,6 +5465,7 @@ const char * hb_subsource_name( int source ) return "UTF-8"; case TX3GSUB: return "TX3G"; + case IMPORTSSA: case SSASUB: return "SSA"; case PGSSUB: diff --git a/libhb/common.h b/libhb/common.h index cfdaced17..1cdee684e 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -164,6 +164,9 @@ hb_subtitle_t *hb_subtitle_copy(const hb_subtitle_t *src); hb_list_t *hb_subtitle_list_copy(const hb_list_t *src); void hb_subtitle_close( hb_subtitle_t **sub ); int hb_subtitle_add(const hb_job_t * job, const hb_subtitle_config_t * subtitlecfg, int track); +int hb_import_subtitle_add( const hb_job_t * job, + const hb_subtitle_config_t * subtitlecfg, + const char *lang_code, int source ); int hb_srt_add(const hb_job_t * job, const hb_subtitle_config_t * subtitlecfg, const char *lang); int hb_subtitle_can_force( int source ); @@ -926,7 +929,9 @@ struct hb_subtitle_s hb_subtitle_config_t config; enum subtype { PICTURESUB, TEXTSUB } format; - enum subsource { VOBSUB, SRTSUB, CC608SUB, /*unused*/CC708SUB, UTF8SUB, TX3GSUB, SSASUB, PGSSUB } source; + enum subsource { VOBSUB, CC608SUB, /*unused*/CC708SUB, + UTF8SUB, TX3GSUB, SSASUB, PGSSUB, + IMPORTSRT, IMPORTSSA, SRTSUB = IMPORTSRT } source; char lang[1024]; char iso639_2[4]; uint32_t attributes; /* Closed Caption, Childrens, Directors etc */ diff --git a/libhb/decssasub.c b/libhb/decssasub.c index 612c7c1c2..4bf9c79d5 100644 --- a/libhb/decssasub.c +++ b/libhb/decssasub.c @@ -34,10 +34,12 @@ struct hb_work_private_s { - // If decoding to PICTURESUB format: - int readOrder; + hb_job_t * job; + hb_subtitle_t * subtitle; - hb_job_t *job; + // SSA Import + FILE * file; + int readOrder; }; #define SSA_VERBOSE_PACKETS 0 @@ -220,27 +222,287 @@ void hb_ssa_style_init(hb_subtitle_style_t *style) style->bg_alpha = 0xFF; } +static int extradataInit( hb_work_private_t * pv ) +{ + int events = 0; + char * events_tag = "[Events]"; + char * format_tag = "Format:"; + int events_len = strlen(events_tag);; + int format_len = strlen(format_tag);; + char * header = NULL; + + while (1) + { + char * line = NULL; + size_t len, size = 0; + + len = getline(&line, &size, pv->file); + if (len < 0) + { + // Incomplete SSA header + free(header); + return 1; + } + if (len > 0) + { + if (header != NULL) + { + char * tmp = header; + header = hb_strdup_printf("%s%s", header, line); + free(tmp); + } + else + { + header = strdup(line); + } + } + if (!events) + { + if (len >= events_len && !strncasecmp(line, events_tag, events_len)) + { + events = 1; + } + } + else + { + if (len >= format_len && !strncasecmp(line, format_tag, format_len)) + { + free(line); + break; + } + if (len > 0) + { + // Improperly formatted SSA header + free(header); + return 1; + } + } + free(line); + } + pv->subtitle->extradata = (uint8_t*)header; + pv->subtitle->extradata_size = strlen(header) + 1; + + return 0; +} + static int decssaInit( hb_work_object_t * w, hb_job_t * job ) { hb_work_private_t * pv; pv = calloc( 1, sizeof( hb_work_private_t ) ); w->private_data = pv; - pv->job = job; + pv->job = job; + pv->subtitle = w->subtitle; + + if (w->fifo_in == NULL && pv->subtitle->config.src_filename != NULL) + { + pv->file = hb_fopen(pv->subtitle->config.src_filename, "r"); + if(pv->file == NULL) + { + hb_error("Could not open the SSA subtitle file '%s'\n", + pv->subtitle->config.src_filename); + goto fail; + } + + // Read SSA header and store in subtitle extradata + if (extradataInit(pv)) + { + goto fail; + } + } + + return 0; + +fail: + if (pv != NULL) + { + if (pv->file != NULL) + { + fclose(pv->file); + } + free(pv); + w->private_data = NULL; + } + return 1; +} + +#define SSA_2_HB_TIME(hr,min,sec,centi) \ + ( 90LL * ( hr * 1000LL * 60 * 60 +\ + min * 1000LL * 60 +\ + sec * 1000LL +\ + centi * 10LL ) ) + +/* + * Parses the start and stop time from the specified SSA packet. + * + * Returns true if parsing failed; false otherwise. + */ +static int parse_timing( char *line, int64_t *start, int64_t *stop ) +{ + /* + * Parse Start and End fields for timing information + */ + int start_hr, start_min, start_sec, start_centi; + int end_hr, end_min, end_sec, end_centi; + // SSA subtitles have an empty layer field (bare ','). The scanf + // format specifier "%*128[^,]" will not match on a bare ','. There + // must be at least one non ',' character in the match. So the format + // specifier is placed directly next to the ':' so that the next + // expected ' ' after the ':' will be the character it matches on + // when there is no layer field. + int numPartsRead = sscanf( (char *) line, "Dialogue:%*128[^,]," + "%d:%d:%d.%d," // Start + "%d:%d:%d.%d,", // End + &start_hr, &start_min, &start_sec, &start_centi, + &end_hr, &end_min, &end_sec, &end_centi ); + if ( numPartsRead != 8 ) + return 1; + + *start = SSA_2_HB_TIME(start_hr, start_min, start_sec, start_centi); + *stop = SSA_2_HB_TIME( end_hr, end_min, end_sec, end_centi); return 0; } +static char * find_field( char * pos, char * end, int fieldNum ) +{ + int curFieldID = 1; + while (pos < end) + { + if ( *pos++ == ',' ) + { + curFieldID++; + if ( curFieldID == fieldNum ) + return pos; + } + } + return NULL; +} + +/* + * SSA line format: + * Dialogue: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0' + * 1 2 3 4 5 6 7 8 9 10 + * + * MKV-SSA packet format: + * ReadOrder,Marked, Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0' + * 1 2 3 4 5 6 7 8 9 + */ +static hb_buffer_t * +decode_line_to_mkv_ssa( hb_work_private_t * pv, char * line, int size ) +{ + hb_buffer_t * out; + + int64_t start, stop; + if (parse_timing(line, &start, &stop)) + { + goto fail; + } + + // Convert the SSA packet to MKV-SSA format, which is what libass expects + char * mkvSSA; + int numPartsRead; + char * styleToTextFields; + char * layerField = malloc(size); + + // SSA subtitles have an empty layer field (bare ','). The scanf + // format specifier "%*128[^,]" will not match on a bare ','. There + // must be at least one non ',' character in the match. So the format + // specifier is placed directly next to the ':' so that the next + // expected ' ' after the ':' will be the character it matches on + // when there is no layer field. + numPartsRead = sscanf( (char *)line, "Dialogue:%128[^,],", layerField ); + if ( numPartsRead != 1 ) + { + free(layerField); + goto fail; + } + + styleToTextFields = find_field( line, line + size, 4 ); + if ( styleToTextFields == NULL ) { + free( layerField ); + goto fail; + } + + // The sscanf conversion above will result in an extra space + // before the layerField. Strip the space. + char *stripLayerField = layerField; + for(; *stripLayerField == ' '; stripLayerField++); + + out = hb_buffer_init( size + 1 ); + mkvSSA = (char*)out->data; + + mkvSSA[0] = '\0'; + sprintf(mkvSSA, "%d", pv->readOrder++); + strcat( mkvSSA, "," ); + strcat( mkvSSA, stripLayerField ); + strcat( mkvSSA, "," ); + strcat( mkvSSA, (char *)styleToTextFields ); + + out->size = strlen(mkvSSA) + 1; + out->s.frametype = HB_FRAME_SUBTITLE; + out->s.start = start; + out->s.duration = stop - start; + out->s.stop = stop; + + if( out->size == 0 ) + { + hb_buffer_close(&out); + } + + free( layerField ); + + return out; + +fail: + hb_log( "decssasub: malformed SSA subtitle packet: %.*s\n", size, line ); + return NULL; +} + +/* + * Read the SSA file and put the entries into the subtitle fifo for all to read + */ +static hb_buffer_t * ssa_read( hb_work_private_t * pv ) +{ + hb_buffer_t * out; + + while (!feof(pv->file)) + { + char * line = NULL; + size_t len, size = 0; + + len = getline(&line, &size, pv->file); + if (len > 0) + { + out = decode_line_to_mkv_ssa(pv, line, len); + if (out != NULL) + { + return out; + } + } + if (len < 0) + { + // Error or EOF + out = hb_buffer_eof_init(); + return out; + } + } + out = hb_buffer_eof_init(); + + return out; +} + static int decssaWork( hb_work_object_t * w, hb_buffer_t ** buf_in, hb_buffer_t ** buf_out ) { - hb_buffer_t * in = *buf_in; - -#if SSA_VERBOSE_PACKETS - printf("\nPACKET(%"PRId64",%"PRId64"): %.*s\n", in->s.start/90, in->s.stop/90, in->size, in->data); -#endif + hb_work_private_t * pv = w->private_data; + hb_buffer_t * in = *buf_in; *buf_in = NULL; + if (in == NULL && pv->file != NULL) + { + in = ssa_read(pv); + } *buf_out = in; if (in->s.flags & HB_BUF_FLAG_EOF) { @@ -254,6 +516,10 @@ static int decssaWork( hb_work_object_t * w, hb_buffer_t ** buf_in, hb_buffer_realloc(in, ++in->size); in->data[in->size - 1] = '\0'; +#if SSA_VERBOSE_PACKETS + printf("\nPACKET(%"PRId64",%"PRId64"): %.*s\n", in->s.start/90, in->s.stop/90, in->size, in->data); +#endif + return HB_WORK_OK; } diff --git a/libhb/hb_json.c b/libhb/hb_json.c index 27df2fd71..2399adf45 100644 --- a/libhb/hb_json.c +++ b/libhb/hb_json.c @@ -823,17 +823,25 @@ hb_dict_t* hb_job_to_dict( const hb_job_t * job ) hb_dict_t *subtitle_dict; hb_subtitle_t *subtitle = hb_list_item(job->list_subtitle, ii); - if (subtitle->source == SRTSUB) + if (subtitle->source == IMPORTSRT || + subtitle->source == IMPORTSSA) { subtitle_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s:{s:o, s:o, s:o}}", "Default", hb_value_bool(subtitle->config.default_track), "Burn", hb_value_bool(subtitle->config.dest == RENDERSUB), "Offset", hb_value_int(subtitle->config.offset), - "SRT", + "Import", + "Format", hb_value_string(subtitle->source == IMPORTSRT ? + "SRT" : "SSA"), "Filename", hb_value_string(subtitle->config.src_filename), - "Language", hb_value_string(subtitle->iso639_2), - "Codeset", hb_value_string(subtitle->config.src_codeset)); + "Language", hb_value_string(subtitle->iso639_2)); + if (subtitle->source == IMPORTSRT) + { + hb_dict_t *import_dict = hb_dict_get(subtitle_dict, "Import"); + hb_dict_set(import_dict, "Codeset", + hb_value_string(subtitle->config.src_codeset)); + } } else { @@ -1509,14 +1517,17 @@ hb_job_t* hb_dict_to_job( hb_handle_t * h, hb_dict_t *dict ) hb_subtitle_config_t sub_config; int track = -1; int burn = 0; - const char *srtfile = NULL; + const char *importfile = NULL; json_int_t offset = 0; result = json_unpack_ex(subtitle_dict, &error, 0, - "{s?i, s?{s:s}}", + "{s?i, s?{s:s}, s?{s:s}}", "Track", unpack_i(&track), + // Support legacy "SRT" import "SRT", - "Filename", unpack_s(&srtfile)); + "Filename", unpack_s(&importfile), + "Import", + "Filename", unpack_s(&importfile)); if (result < 0) { hb_error("json unpack failure: %s", error.text); @@ -1524,7 +1535,7 @@ hb_job_t* hb_dict_to_job( hb_handle_t * h, hb_dict_t *dict ) return NULL; } // Embedded subtitle track - if (track >= 0 && srtfile == NULL) + if (track >= 0 && importfile == NULL) { hb_subtitle_t *subtitle; subtitle = hb_list_item(job->title->list_subtitle, track); @@ -1548,22 +1559,30 @@ hb_job_t* hb_dict_to_job( hb_handle_t * h, hb_dict_t *dict ) hb_subtitle_add(job, &sub_config, track); } } - else if (srtfile != NULL) + else if (importfile != NULL) { - strncpy(sub_config.src_filename, srtfile, 255); + strncpy(sub_config.src_filename, importfile, 255); sub_config.src_filename[255] = 0; - const char *srtlang = "und"; - const char *srtcodeset = "UTF-8"; + const char * lang = "und"; + const char * srtcodeset = "UTF-8"; + const char * format = "SRT"; + int source = IMPORTSRT; result = json_unpack_ex(subtitle_dict, &error, 0, - "{s?b, s?b, s?I, " // Common - "s?{s?s, s?s, s?s}}", // SRT + "{s?b, s?b, s?I, " // Common + "s?{s?s, s?s, s?s}," // Legacy SRT settings + "s?{s?s, s?s, s?s, s?s}}", // Import settings "Default", unpack_b(&sub_config.default_track), "Burn", unpack_b(&burn), "Offset", unpack_I(&offset), "SRT", - "Filename", unpack_s(&srtfile), - "Language", unpack_s(&srtlang), + "Filename", unpack_s(&importfile), + "Language", unpack_s(&lang), + "Codeset", unpack_s(&srtcodeset), + "Import", + "Format", unpack_s(&format), + "Filename", unpack_s(&importfile), + "Language", unpack_s(&lang), "Codeset", unpack_s(&srtcodeset)); if (result < 0) { @@ -1575,7 +1594,11 @@ hb_job_t* hb_dict_to_job( hb_handle_t * h, hb_dict_t *dict ) sub_config.dest = burn ? RENDERSUB : PASSTHRUSUB; strncpy(sub_config.src_codeset, srtcodeset, 39); sub_config.src_codeset[39] = 0; - hb_srt_add(job, &sub_config, srtlang); + if (!strcasecmp(format, "SSA")) + { + source = IMPORTSSA; + } + hb_import_subtitle_add(job, &sub_config, lang, source); } } } diff --git a/libhb/muxavformat.c b/libhb/muxavformat.c index 806620b69..60eba066f 100644 --- a/libhb/muxavformat.c +++ b/libhb/muxavformat.c @@ -842,9 +842,10 @@ static int avformatInit( hb_mux_object_t * m ) case CC608SUB: case CC708SUB: case TX3GSUB: - case SRTSUB: case UTF8SUB: case SSASUB: + case IMPORTSRT: + case IMPORTSSA: { if (job->mux == HB_MUX_AV_MP4) { diff --git a/libhb/reader.c b/libhb/reader.c index 4099a54f6..f200152bb 100644 --- a/libhb/reader.c +++ b/libhb/reader.c @@ -432,8 +432,10 @@ static void reader_send_eof( hb_work_private_t * r ) hb_subtitle_t *subtitle; for (ii = 0; (subtitle = hb_list_item(r->job->list_subtitle, ii)); ++ii) { - if (subtitle->fifo_in && subtitle->source != SRTSUB) + if (subtitle->fifo_in) + { push_buf(r, subtitle->fifo_in, hb_buffer_eof_init()); + } } hb_log("reader: done. %d scr changes", r->demux.scr_changes); } diff --git a/libhb/rendersub.c b/libhb/rendersub.c index 88b4bfdf1..866e277f4 100644 --- a/libhb/rendersub.c +++ b/libhb/rendersub.c @@ -981,7 +981,8 @@ static int hb_rendersub_post_init( hb_filter_object_t * filter, hb_job_t *job ) return ssa_post_init( filter, job ); } break; - case SRTSUB: + case IMPORTSRT: + case IMPORTSSA: case UTF8SUB: case TX3GSUB: { @@ -1024,7 +1025,8 @@ static int hb_rendersub_work( hb_filter_object_t * filter, return ssa_work( filter, buf_in, buf_out ); } break; - case SRTSUB: + case IMPORTSRT: + case IMPORTSSA: case CC608SUB: case UTF8SUB: case TX3GSUB: @@ -1065,7 +1067,8 @@ static void hb_rendersub_close( hb_filter_object_t * filter ) ssa_close( filter ); } break; - case SRTSUB: + case IMPORTSRT: + case IMPORTSSA: case CC608SUB: case UTF8SUB: case TX3GSUB: diff --git a/libhb/stream.c b/libhb/stream.c index 427e10209..86aab1b39 100644 --- a/libhb/stream.c +++ b/libhb/stream.c @@ -5372,7 +5372,8 @@ static void add_ffmpeg_subtitle( hb_title_t *title, hb_stream_t *stream, int id snprintf(subtitle->lang, sizeof( subtitle->lang ), "%s [%s]", strlen(lang->native_name) ? lang->native_name : lang->eng_name, hb_subsource_name(subtitle->source)); - strncpy(subtitle->iso639_2, lang->iso639_2, 4); + strncpy(subtitle->iso639_2, lang->iso639_2, 3); + subtitle->iso639_2[3] = 0; // Copy the extradata for the subtitle track if (codecpar->extradata != NULL) diff --git a/libhb/work.c b/libhb/work.c index ac5ae5533..5c7efb0b7 100644 --- a/libhb/work.c +++ b/libhb/work.c @@ -600,7 +600,7 @@ void hb_display_job_info(hb_job_t *job) subtitle->lang, subtitle->track, subtitle->id, subtitle->format == PICTURESUB ? "Picture" : "Text"); } - else if( subtitle->source == SRTSUB ) + else if (subtitle->source == IMPORTSRT) { /* For SRT, print offset and charset too */ hb_log(" * subtitle track %d, %s (track %d, id 0x%x, Text) -> " @@ -1619,10 +1619,10 @@ static void do_job(hb_job_t *job) // Since that number is unbounded, the FIFO must be made // (effectively) unbounded in capacity. subtitle->fifo_raw = hb_fifo_init( FIFO_UNBOUNDED, FIFO_UNBOUNDED_WAKE ); - if (w->id != WORK_DECSRTSUB) + // Check if input comes from a file. + if (subtitle->source != IMPORTSRT && + subtitle->source != IMPORTSSA) { - // decsrtsub is a buffer source like reader. It's input comes - // from a file. subtitle->fifo_in = hb_fifo_init( FIFO_SMALL, FIFO_SMALL_WAKE ); } if (!job->indepth_scan) |