2 This file is part of the HandBrake source code.
3 Homepage: <http://handbrake.fr/>.
4 It may be used under the terms of the GNU General Public License. */
7 * Converts SSA subtitles to either:
8 * (1) TEXTSUB format: UTF-8 subtitles with limited HTML-style markup (<b>, <i>, <u>), or
9 * (2) PICTURESUB format, using libass.
11 * SSA format references:
12 * http://www.matroska.org/technical/specs/subtitles/ssa.html
13 * http://moodub.free.fr/video/ass-specs.doc
14 * vlc-1.0.4/modules/codec/subtitles/subsass.c:ParseSSAString
18 * vlc-1.0.4/modules/codec/libass.c
20 * @author David Foster (davidfstr)
29 struct hb_work_private_s
31 // If decoding to PICTURESUB format:
33 ASS_Renderer *renderer;
44 // "<b></b>".len + "<i></i>".len + "<u></u>".len
45 #define MAX_OVERHEAD_PER_OVERRIDE (7 * 3)
47 #define SSA_2_HB_TIME(hr,min,sec,centi) \
48 ( 90L * ( hr * 1000L * 60 * 60 +\
53 static StyleSet ssa_parse_style_override( uint8_t *pos, StyleSet prevStyles )
55 StyleSet nextStyles = prevStyles;
58 // Skip over leading '{' or last '\\'
61 // Scan for next \code
62 while ( *pos != '\\' && *pos != '}' && *pos != '\0' ) pos++;
65 // End of style override block
69 // If next chars are \[biu][01], interpret it
70 if ( strchr("biu", pos[1]) && strchr("01", pos[2]) )
73 pos[1] == 'b' ? BOLD :
74 pos[1] == 'i' ? ITALIC :
75 pos[1] == 'u' ? UNDERLINE : 0;
76 int enabled = (pos[2] == '1');
80 nextStyles |= styleID;
84 nextStyles &= ~styleID;
91 static void ssa_append_html_tags_for_style_change(
92 uint8_t **dst, StyleSet prevStyles, StyleSet nextStyles )
94 #define APPEND(str) { \
96 while (*src) { *(*dst)++ = *src++; } \
99 // Reverse-order close all previous styles
100 if (prevStyles & UNDERLINE) APPEND("</u>");
101 if (prevStyles & ITALIC) APPEND("</i>");
102 if (prevStyles & BOLD) APPEND("</b>");
104 // Forward-order open all next styles
105 if (nextStyles & BOLD) APPEND("<b>");
106 if (nextStyles & ITALIC) APPEND("<i>");
107 if (nextStyles & UNDERLINE) APPEND("<u>");
112 static hb_buffer_t *ssa_decode_line_to_utf8( uint8_t *in_data, int in_size, int in_sequence );
113 static hb_buffer_t *ssa_decode_line_to_picture( hb_work_object_t * w, uint8_t *in_data, int in_size, int in_sequence );
116 * Decodes a single SSA packet to one or more TEXTSUB or PICTURESUB subtitle packets.
119 * ( Dialogue: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text CR LF ) +
120 * 1 2 3 4 5 6 7 8 9 10
122 static hb_buffer_t *ssa_decode_packet( hb_work_object_t * w, hb_buffer_t *in )
124 // Store NULL after the end of the buffer to make using string processing safe
125 hb_buffer_realloc( in, in->size + 1 );
126 in->data[in->size] = '\0';
128 hb_buffer_t *out_list = NULL;
129 hb_buffer_t **nextPtr = &out_list;
131 const char *EOL = "\r\n";
132 char *curLine, *curLine_parserData;
133 for ( curLine = strtok_r( (char *) in->data, EOL, &curLine_parserData );
135 curLine = strtok_r( NULL, EOL, &curLine_parserData ) )
137 // Skip empty lines and spaces between adjacent CR and LF
138 if (curLine[0] == '\0')
141 // Decode an individual SSA line
143 if ( w->subtitle->config.dest == PASSTHRUSUB ) {
144 out = ssa_decode_line_to_utf8( (uint8_t *) curLine, strlen( curLine ), in->sequence );
148 // We shouldn't be storing the extra NULL character,
149 // but the MP4 muxer expects this, unfortunately.
150 if ( out->size > 0 && out->data[out->size - 1] != '\0' ) {
151 // NOTE: out->size remains unchanged
152 hb_buffer_realloc( out, out->size + 1 );
153 out->data[out->size] = '\0';
156 // If the input packet was non-empty, do not pass through
157 // an empty output packet (even if the subtitle was empty),
158 // as this would be interpreted as an end-of-stream
159 if ( in->size > 0 && out->size == 0 ) {
160 hb_buffer_close(&out);
163 } else if ( w->subtitle->config.dest == RENDERSUB ) {
164 out = ssa_decode_line_to_picture( w, (uint8_t *) curLine, strlen( curLine ), in->sequence );
169 // Append 'out' to 'out_list'
171 nextPtr = &out->next;
174 // For point-to-point encoding, when the start time of the stream
175 // may be offset, the timestamps of the subtitles must be offset as well.
177 // HACK: Here we are making the assumption that, under normal circumstances,
178 // the output display time of the first output packet is equal to the
179 // display time of the input packet.
181 // During point-to-point encoding, the display time of the input
182 // packet will be offset to compensate.
184 // Therefore we offset all of the output packets by a slip amount
185 // such that first output packet's display time aligns with the
186 // input packet's display time. This should give the correct time
187 // when point-to-point encoding is in effect.
188 if (out_list && out_list->start > in->start)
190 int64_t slip = out_list->start - in->start;
206 * Parses the start and stop time from the specified SSA packet.
208 * Returns true if parsing failed; false otherwise.
210 static int parse_timing_from_ssa_packet( char *in_data, int64_t *in_start, int64_t *in_stop )
213 * Parse Start and End fields for timing information
215 int start_hr, start_min, start_sec, start_centi;
216 int end_hr, end_min, end_sec, end_centi;
217 int numPartsRead = sscanf( (char *) in_data, "Dialogue: %*128[^,],"
218 "%d:%d:%d.%d," // Start
219 "%d:%d:%d.%d,", // End
220 &start_hr, &start_min, &start_sec, &start_centi,
221 &end_hr, &end_min, &end_sec, &end_centi );
222 if ( numPartsRead != 8 )
225 *in_start = SSA_2_HB_TIME(start_hr, start_min, start_sec, start_centi);
226 *in_stop = SSA_2_HB_TIME( end_hr, end_min, end_sec, end_centi);
231 static uint8_t *find_field( uint8_t *pos, uint8_t *end, int fieldNum )
239 if ( curFieldID == fieldNum )
248 * Dialogue: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0'
249 * 1 2 3 4 5 6 7 8 9 10
251 static hb_buffer_t *ssa_decode_line_to_utf8( uint8_t *in_data, int in_size, int in_sequence )
253 uint8_t *pos = in_data;
254 uint8_t *end = in_data + in_size;
256 // Parse values for in->start and in->stop
257 int64_t in_start, in_stop;
258 if ( parse_timing_from_ssa_packet( (char *) in_data, &in_start, &in_stop ) )
261 uint8_t *textFieldPos = find_field( pos, end, 10 );
262 if ( textFieldPos == NULL )
265 // Count the number of style overrides in the Text field
266 int numStyleOverrides = 0;
276 int maxOutputSize = (end - textFieldPos) + ((numStyleOverrides + 1) * MAX_OVERHEAD_PER_OVERRIDE);
277 hb_buffer_t *out = hb_buffer_init( maxOutputSize );
282 * The Text field contains plain text marked up with:
284 * (2) '\N' -> newline
285 * (3) curly-brace control codes like '{\k44}' -> HTML tags / strip
287 * Perform the above conversions and copy it to the output packet
289 StyleSet prevStyles = 0;
290 uint8_t *dst = out->data;
294 if ( pos[0] == '\\' && pos[1] == 'n' )
299 else if ( pos[0] == '\\' && pos[1] == 'N' )
304 else if ( pos[0] == '{' )
306 // Parse SSA style overrides and append appropriate HTML style tags
307 StyleSet nextStyles = ssa_parse_style_override( pos, prevStyles );
308 ssa_append_html_tags_for_style_change( &dst, prevStyles, nextStyles );
309 prevStyles = nextStyles;
311 // Skip past SSA control code
312 while ( pos < end && *pos != '}' ) pos++;
313 if ( pos < end && *pos == '}' ) pos++;
317 // Copy raw character
322 // Append closing HTML style tags
323 ssa_append_html_tags_for_style_change( &dst, prevStyles, 0 );
325 // Trim output buffer to the actual amount of data written
326 out->size = dst - out->data;
328 // Copy metadata from the input packet to the output packet
329 out->start = in_start;
331 out->sequence = in_sequence;
336 hb_log( "decssasub: malformed SSA subtitle packet: %.*s\n", in_size, in_data );
342 * Dialogue: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0'
343 * 1 2 3 4 5 6 7 8 9 10
345 * MKV-SSA packet format:
346 * ReadOrder,Marked, Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0'
349 static hb_buffer_t *ssa_decode_line_to_picture( hb_work_object_t * w, uint8_t *in_data, int in_size, int in_sequence )
351 hb_work_private_t * pv = w->private_data;
353 // Parse values for in->start and in->stop
354 int64_t in_start, in_stop;
355 if ( parse_timing_from_ssa_packet( (char *) in_data, &in_start, &in_stop ) )
358 // Convert the SSA packet to MKV-SSA format, which is what libass expects
362 char *layerField = malloc( in_size );
363 int numPartsRead = sscanf( (char *) in_data, "Dialogue: %128[^,],", layerField );
364 if ( numPartsRead != 1 )
367 char *styleToTextFields = (char *) find_field( in_data, in_data + in_size, 4 );
368 if ( styleToTextFields == NULL ) {
373 mkvIn = malloc( in_size + 1 );
375 sprintf(mkvIn, "%d", pv->readOrder++); // ReadOrder: make this up
376 strcat( mkvIn, "," );
377 strcat( mkvIn, layerField );
378 strcat( mkvIn, "," );
379 strcat( mkvIn, (char *) styleToTextFields );
381 mkvInSize = strlen(mkvIn);
386 // Parse MKV-SSA packet
387 ass_process_chunk( pv->ssaTrack, mkvIn, mkvInSize, in_start / 90, (in_stop - in_start) / 90 );
391 // TODO: To support things like karaoke, it won't be sufficient to only generate
392 // new subtitle pictures when there are subtitle packets. Rather, pictures will
393 // need to be generated potentially continuously.
395 // Until "karaoke support" is implemented, make an educated guess about the
396 // timepoint within the subtitle that should be rendered. I guess the midpoint.
397 int64_t renderTime = ( in_start + in_stop ) / 2;
400 ASS_Image *frameList = ass_render_frame( pv->renderer, pv->ssaTrack, renderTime / 90, &changed );
401 if ( !changed || !frameList )
406 for (curFrame = frameList; curFrame; curFrame = curFrame->next)
409 hb_buffer_t *outSubpictureList = NULL;
410 hb_buffer_t **outSubpictureListTailPtr = &outSubpictureList;
412 // Generate a PICTURESUB packet from the frames
414 for (frame = frameList; frame; frame = frame->next) {
415 // Allocate pixmap where drawing will be done
416 uint8_t *rgba = calloc(frame->w * frame->h * 4, 1);
418 unsigned r = (frame->color >> 24) & 0xff;
419 unsigned g = (frame->color >> 16) & 0xff;
420 unsigned b = (frame->color >> 8) & 0xff;
421 unsigned a = (frame->color ) & 0xff;
424 for (y = 0; y < frame->h; y++) {
425 for (x = 0; x < frame->w; x++) {
426 unsigned srcAlphaPrenormalized = frame->bitmap[y*frame->stride + x];
427 unsigned srcAlpha = (255 - a) * srcAlphaPrenormalized / 255;
429 uint8_t *dst = &rgba[(y*frame->w + x) * 4];
430 unsigned oldDstAlpha = dst[3];
432 if (oldDstAlpha == 0) {
439 dst[3] = 255 - ( 255 - dst[3] ) * ( 255 - srcAlpha ) / 255;
441 dst[0] = ( dst[0] * oldDstAlpha * (255-srcAlpha) / 255 + r * srcAlpha ) / dst[3];
442 dst[1] = ( dst[1] * oldDstAlpha * (255-srcAlpha) / 255 + g * srcAlpha ) / dst[3];
443 dst[2] = ( dst[2] * oldDstAlpha * (255-srcAlpha) / 255 + b * srcAlpha ) / dst[3];
449 // Generate output subpicture (in PICTURESUB format)
450 hb_buffer_t *out = hb_buffer_init(frame->w * frame->h * 4);
451 out->x = frame->dst_x;
452 out->y = frame->dst_y;
453 out->width = frame->w;
454 out->height = frame->h;
457 int numPixels = frame->w * frame->h;
458 for (i = 0; i < numPixels; i++) {
459 uint8_t *srcRgba = &rgba[i * 4];
461 uint8_t *dstY = &out->data[(numPixels * 0) + i];
462 uint8_t *dstA = &out->data[(numPixels * 1) + i];
463 uint8_t *dstU = &out->data[(numPixels * 2) + i];
464 uint8_t *dstV = &out->data[(numPixels * 3) + i];
466 int srcYuv = hb_rgb2yuv((srcRgba[0] << 16) | (srcRgba[1] << 8) | (srcRgba[2] << 0));
467 int srcA = srcRgba[3];
469 *dstY = (srcYuv >> 16) & 0xff;
470 *dstU = (srcYuv >> 8 ) & 0xff;
471 *dstV = (srcYuv >> 0 ) & 0xff;
472 *dstA = srcA / 16; // HB's max alpha value is 16
477 *outSubpictureListTailPtr = out;
478 outSubpictureListTailPtr = &out->next_subpicture;
481 // NOTE: The subpicture list is actually considered a single packet by most other code
482 hb_buffer_t *out = outSubpictureList;
484 // Copy metadata from the input packet to the output packet
485 out->start = in_start;
487 out->sequence = in_sequence;
492 hb_log( "decssasub: malformed SSA subtitle packet: %.*s\n", in_size, in_data );
496 static void ssa_log(int level, const char *fmt, va_list args, void *data)
498 if ( level < 5 ) // same as default verbosity when no callback is set
501 if ( vasprintf( &msg, fmt, args ) < 0 )
503 hb_log( "decssasub: could not report libass message\n" );
506 hb_log( "[ass] %s", msg ); // no need for extra '\n' because libass sends it
512 static int decssaInit( hb_work_object_t * w, hb_job_t * job )
514 hb_work_private_t * pv;
516 pv = calloc( 1, sizeof( hb_work_private_t ) );
517 w->private_data = pv;
519 if ( w->subtitle->config.dest == RENDERSUB ) {
520 pv->ssa = ass_library_init();
522 hb_log( "decssasub: libass initialization failed\n" );
526 // Redirect libass output to hb_log
527 ass_set_message_cb( pv->ssa, ssa_log, NULL );
529 // Load embedded fonts
530 hb_list_t * list_attachment = job->title->list_attachment;
532 for ( i = 0; i < hb_list_count(list_attachment); i++ )
534 hb_attachment_t * attachment = hb_list_item( list_attachment, i );
536 if ( attachment->type == FONT_TTF_ATTACH )
546 ass_set_extract_fonts( pv->ssa, 1 );
547 ass_set_style_overrides( pv->ssa, NULL );
549 pv->renderer = ass_renderer_init( pv->ssa );
550 if ( !pv->renderer ) {
551 hb_log( "decssasub: renderer initialization failed\n" );
555 ass_set_use_margins( pv->renderer, 0 );
556 ass_set_hinting( pv->renderer, ASS_HINTING_LIGHT ); // VLC 1.0.4 uses this
557 ass_set_font_scale( pv->renderer, 1.0 );
558 ass_set_line_spacing( pv->renderer, 1.0 );
560 // Setup default font family
562 // SSA v4.00 requires that "Arial" be the default font
563 const char *font = NULL;
564 const char *family = "Arial";
565 // NOTE: This can sometimes block for several *seconds*.
566 // It seems that process_fontdata() for some embedded fonts is slow.
567 ass_set_fonts( pv->renderer, font, family, /*haveFontConfig=*/1, NULL, 1 );
570 pv->ssaTrack = ass_new_track( pv->ssa );
571 if ( !pv->ssaTrack ) {
572 hb_log( "decssasub: ssa track initialization failed\n" );
576 // NOTE: The codec extradata is expected to be in MKV format
577 ass_process_codec_private( pv->ssaTrack,
578 (char *) w->subtitle->extradata, w->subtitle->extradata_size );
580 int originalWidth = job->title->width;
581 int originalHeight = job->title->height;
582 ass_set_frame_size( pv->renderer, originalWidth, originalHeight);
583 ass_set_aspect_ratio( pv->renderer, /*dar=*/1.0, /*sar=*/1.0 );
589 static int decssaWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
590 hb_buffer_t ** buf_out )
592 hb_buffer_t * in = *buf_in;
593 hb_buffer_t * out_list = NULL;
595 if ( in->size > 0 ) {
596 out_list = ssa_decode_packet(w, in);
598 out_list = hb_buffer_init( 0 );
601 // Dispose the input packet, as it is no longer needed
602 hb_buffer_close(&in);
609 static void decssaClose( hb_work_object_t * w )
611 hb_work_private_t * pv = w->private_data;
614 ass_free_track( pv->ssaTrack );
616 ass_renderer_done( pv->renderer );
618 ass_library_done( pv->ssa );
620 free( w->private_data );
623 hb_work_object_t hb_decssasub =
626 "SSA Subtitle Decoder",