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;
178 * Parses the start and stop time from the specified SSA packet.
180 * Returns true if parsing failed; false otherwise.
182 static int parse_timing_from_ssa_packet( char *in_data, int64_t *in_start, int64_t *in_stop )
185 * Parse Start and End fields for timing information
187 int start_hr, start_min, start_sec, start_centi;
188 int end_hr, end_min, end_sec, end_centi;
189 int numPartsRead = sscanf( (char *) in_data, "Dialogue: %*128[^,],"
190 "%d:%d:%d.%d," // Start
191 "%d:%d:%d.%d,", // End
192 &start_hr, &start_min, &start_sec, &start_centi,
193 &end_hr, &end_min, &end_sec, &end_centi );
194 if ( numPartsRead != 8 )
197 *in_start = SSA_2_HB_TIME(start_hr, start_min, start_sec, start_centi);
198 *in_stop = SSA_2_HB_TIME( end_hr, end_min, end_sec, end_centi);
203 static uint8_t *find_field( uint8_t *pos, uint8_t *end, int fieldNum )
211 if ( curFieldID == fieldNum )
220 * Dialogue: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0'
221 * 1 2 3 4 5 6 7 8 9 10
223 static hb_buffer_t *ssa_decode_line_to_utf8( uint8_t *in_data, int in_size, int in_sequence )
225 uint8_t *pos = in_data;
226 uint8_t *end = in_data + in_size;
228 // Parse values for in->start and in->stop
229 int64_t in_start, in_stop;
230 if ( parse_timing_from_ssa_packet( (char *) in_data, &in_start, &in_stop ) )
233 uint8_t *textFieldPos = find_field( pos, end, 10 );
234 if ( textFieldPos == NULL )
237 // Count the number of style overrides in the Text field
238 int numStyleOverrides = 0;
248 int maxOutputSize = (end - textFieldPos) + ((numStyleOverrides + 1) * MAX_OVERHEAD_PER_OVERRIDE);
249 hb_buffer_t *out = hb_buffer_init( maxOutputSize );
254 * The Text field contains plain text marked up with:
256 * (2) '\N' -> newline
257 * (3) curly-brace control codes like '{\k44}' -> HTML tags / strip
259 * Perform the above conversions and copy it to the output packet
261 StyleSet prevStyles = 0;
262 uint8_t *dst = out->data;
266 if ( pos[0] == '\\' && pos[1] == 'n' )
271 else if ( pos[0] == '\\' && pos[1] == 'N' )
276 else if ( pos[0] == '{' )
278 // Parse SSA style overrides and append appropriate HTML style tags
279 StyleSet nextStyles = ssa_parse_style_override( pos, prevStyles );
280 ssa_append_html_tags_for_style_change( &dst, prevStyles, nextStyles );
281 prevStyles = nextStyles;
283 // Skip past SSA control code
284 while ( pos < end && *pos != '}' ) pos++;
285 if ( pos < end && *pos == '}' ) pos++;
289 // Copy raw character
294 // Append closing HTML style tags
295 ssa_append_html_tags_for_style_change( &dst, prevStyles, 0 );
297 // Trim output buffer to the actual amount of data written
298 out->size = dst - out->data;
300 // Copy metadata from the input packet to the output packet
301 out->start = in_start;
303 out->sequence = in_sequence;
308 hb_log( "decssasub: malformed SSA subtitle packet: %.*s\n", in_size, in_data );
314 * Dialogue: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0'
315 * 1 2 3 4 5 6 7 8 9 10
317 * MKV-SSA packet format:
318 * ReadOrder,Marked, Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0'
321 static hb_buffer_t *ssa_decode_line_to_picture( hb_work_object_t * w, uint8_t *in_data, int in_size, int in_sequence )
323 hb_work_private_t * pv = w->private_data;
325 // Parse values for in->start and in->stop
326 int64_t in_start, in_stop;
327 if ( parse_timing_from_ssa_packet( (char *) in_data, &in_start, &in_stop ) )
330 // Convert the SSA packet to MKV-SSA format, which is what libass expects
334 char *layerField = malloc( in_size );
335 int numPartsRead = sscanf( (char *) in_data, "Dialogue: %128[^,],", layerField );
336 if ( numPartsRead != 1 )
339 char *styleToTextFields = (char *) find_field( in_data, in_data + in_size, 4 );
340 if ( styleToTextFields == NULL ) {
345 mkvIn = malloc( in_size + 1 );
347 sprintf(mkvIn, "%d", pv->readOrder++); // ReadOrder: make this up
348 strcat( mkvIn, "," );
349 strcat( mkvIn, layerField );
350 strcat( mkvIn, "," );
351 strcat( mkvIn, (char *) styleToTextFields );
353 mkvInSize = strlen(mkvIn);
358 // Parse MKV-SSA packet
359 ass_process_chunk( pv->ssaTrack, mkvIn, mkvInSize, in_start / 90, (in_stop - in_start) / 90 );
363 // TODO: To support things like karaoke, it won't be sufficient to only generate
364 // new subtitle pictures when there are subtitle packets. Rather, pictures will
365 // need to be generated potentially continuously.
367 // Until "karaoke support" is implemented, make an educated guess about the
368 // timepoint within the subtitle that should be rendered. I guess the midpoint.
369 int64_t renderTime = ( in_start + in_stop ) / 2;
372 ASS_Image *frameList = ass_render_frame( pv->renderer, pv->ssaTrack, renderTime / 90, &changed );
373 if ( !changed || !frameList )
378 for (curFrame = frameList; curFrame; curFrame = curFrame->next)
381 hb_buffer_t *outSubpictureList = NULL;
382 hb_buffer_t **outSubpictureListTailPtr = &outSubpictureList;
384 // Generate a PICTURESUB packet from the frames
386 for (frame = frameList; frame; frame = frame->next) {
387 // Allocate pixmap where drawing will be done
388 uint8_t *rgba = calloc(frame->w * frame->h * 4, 1);
390 unsigned r = (frame->color >> 24) & 0xff;
391 unsigned g = (frame->color >> 16) & 0xff;
392 unsigned b = (frame->color >> 8) & 0xff;
393 unsigned a = (frame->color ) & 0xff;
396 for (y = 0; y < frame->h; y++) {
397 for (x = 0; x < frame->w; x++) {
398 unsigned srcAlphaPrenormalized = frame->bitmap[y*frame->stride + x];
399 unsigned srcAlpha = (255 - a) * srcAlphaPrenormalized / 255;
401 uint8_t *dst = &rgba[(y*frame->w + x) * 4];
402 unsigned oldDstAlpha = dst[3];
404 if (oldDstAlpha == 0) {
411 dst[3] = 255 - ( 255 - dst[3] ) * ( 255 - srcAlpha ) / 255;
413 dst[0] = ( dst[0] * oldDstAlpha * (255-srcAlpha) / 255 + r * srcAlpha ) / dst[3];
414 dst[1] = ( dst[1] * oldDstAlpha * (255-srcAlpha) / 255 + g * srcAlpha ) / dst[3];
415 dst[2] = ( dst[2] * oldDstAlpha * (255-srcAlpha) / 255 + b * srcAlpha ) / dst[3];
421 // Generate output subpicture (in PICTURESUB format)
422 hb_buffer_t *out = hb_buffer_init(frame->w * frame->h * 4);
423 out->x = frame->dst_x;
424 out->y = frame->dst_y;
425 out->width = frame->w;
426 out->height = frame->h;
429 int numPixels = frame->w * frame->h;
430 for (i = 0; i < numPixels; i++) {
431 uint8_t *srcRgba = &rgba[i * 4];
433 uint8_t *dstY = &out->data[(numPixels * 0) + i];
434 uint8_t *dstA = &out->data[(numPixels * 1) + i];
435 uint8_t *dstU = &out->data[(numPixels * 2) + i];
436 uint8_t *dstV = &out->data[(numPixels * 3) + i];
438 int srcYuv = hb_rgb2yuv((srcRgba[0] << 16) | (srcRgba[1] << 8) | (srcRgba[2] << 0));
439 int srcA = srcRgba[3];
441 *dstY = (srcYuv >> 16) & 0xff;
442 *dstU = (srcYuv >> 8 ) & 0xff;
443 *dstV = (srcYuv >> 0 ) & 0xff;
444 *dstA = srcA / 16; // HB's max alpha value is 16
449 *outSubpictureListTailPtr = out;
450 outSubpictureListTailPtr = &out->next_subpicture;
453 // NOTE: The subpicture list is actually considered a single packet by most other code
454 hb_buffer_t *out = outSubpictureList;
456 // Copy metadata from the input packet to the output packet
457 out->start = in_start;
459 out->sequence = in_sequence;
464 hb_log( "decssasub: malformed SSA subtitle packet: %.*s\n", in_size, in_data );
468 static void ssa_log(int level, const char *fmt, va_list args, void *data)
470 if ( level < 5 ) // same as default verbosity when no callback is set
473 if ( vasprintf( &msg, fmt, args ) < 0 )
475 hb_log( "decssasub: could not report libass message\n" );
478 hb_log( "[ass] %s", msg ); // no need for extra '\n' because libass sends it
484 static int decssaInit( hb_work_object_t * w, hb_job_t * job )
486 hb_work_private_t * pv;
488 pv = calloc( 1, sizeof( hb_work_private_t ) );
489 w->private_data = pv;
491 if ( w->subtitle->config.dest == RENDERSUB ) {
492 pv->ssa = ass_library_init();
494 hb_log( "decssasub: libass initialization failed\n" );
498 // Redirect libass output to hb_log
499 ass_set_message_cb( pv->ssa, ssa_log, NULL );
501 // Load embedded fonts
502 hb_list_t * list_attachment = job->title->list_attachment;
504 for ( i = 0; i < hb_list_count(list_attachment); i++ )
506 hb_attachment_t * attachment = hb_list_item( list_attachment, i );
508 if ( attachment->type == FONT_TTF_ATTACH )
518 ass_set_extract_fonts( pv->ssa, 1 );
519 ass_set_style_overrides( pv->ssa, NULL );
521 pv->renderer = ass_renderer_init( pv->ssa );
522 if ( !pv->renderer ) {
523 hb_log( "decssasub: renderer initialization failed\n" );
527 ass_set_use_margins( pv->renderer, 0 );
528 ass_set_hinting( pv->renderer, ASS_HINTING_LIGHT ); // VLC 1.0.4 uses this
529 ass_set_font_scale( pv->renderer, 1.0 );
530 ass_set_line_spacing( pv->renderer, 1.0 );
532 // Setup default font family
534 // SSA v4.00 requires that "Arial" be the default font
535 const char *font = NULL;
536 const char *family = "Arial";
537 // NOTE: This can sometimes block for several *seconds*.
538 // It seems that process_fontdata() for some embedded fonts is slow.
539 ass_set_fonts( pv->renderer, font, family, /*haveFontConfig=*/1, NULL, 1 );
542 pv->ssaTrack = ass_new_track( pv->ssa );
543 if ( !pv->ssaTrack ) {
544 hb_log( "decssasub: ssa track initialization failed\n" );
548 // NOTE: The codec extradata is expected to be in MKV format
549 ass_process_codec_private( pv->ssaTrack,
550 (char *) w->subtitle->extradata, w->subtitle->extradata_size );
552 int originalWidth = job->title->width;
553 int originalHeight = job->title->height;
554 ass_set_frame_size( pv->renderer, originalWidth, originalHeight);
555 ass_set_aspect_ratio( pv->renderer, /*dar=*/1.0, /*sar=*/1.0 );
561 static int decssaWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
562 hb_buffer_t ** buf_out )
564 hb_buffer_t * in = *buf_in;
565 hb_buffer_t * out_list = NULL;
567 if ( in->size > 0 ) {
568 out_list = ssa_decode_packet(w, in);
570 out_list = hb_buffer_init( 0 );
573 // Dispose the input packet, as it is no longer needed
574 hb_buffer_close(&in);
581 static void decssaClose( hb_work_object_t * w )
583 hb_work_private_t * pv = w->private_data;
586 ass_free_track( pv->ssaTrack );
588 ass_renderer_done( pv->renderer );
590 ass_library_done( pv->ssa );
592 free( w->private_data );
595 hb_work_object_t hb_decssasub =
598 "SSA Subtitle Decoder",