diff options
author | John Stebbins <[email protected]> | 2019-01-05 16:53:50 -0700 |
---|---|---|
committer | John Stebbins <[email protected]> | 2019-01-14 13:36:08 -0800 |
commit | 36a9a9f63aaf8dad0e84605354e5e9d4359429eb (patch) | |
tree | 84f26ffe3a82b6272e6fb205bddc35f26f8f8c3d /libhb/decssasub.c | |
parent | 776c2d3d93c08c6cc12d9fc42bc290c3e57772ff (diff) |
Add SSA subtitle import
Diffstat (limited to 'libhb/decssasub.c')
-rw-r--r-- | libhb/decssasub.c | 284 |
1 files changed, 275 insertions, 9 deletions
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; } |