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 #define SSA_VERBOSE_PACKETS 0
55 static StyleSet ssa_parse_style_override( uint8_t *pos, StyleSet prevStyles )
57 StyleSet nextStyles = prevStyles;
60 // Skip over leading '{' or last '\\'
63 // Scan for next \code
64 while ( *pos != '\\' && *pos != '}' && *pos != '\0' ) pos++;
67 // End of style override block
71 // If next chars are \[biu][01], interpret it
72 if ( strchr("biu", pos[1]) && strchr("01", pos[2]) )
75 pos[1] == 'b' ? BOLD :
76 pos[1] == 'i' ? ITALIC :
77 pos[1] == 'u' ? UNDERLINE : 0;
78 int enabled = (pos[2] == '1');
82 nextStyles |= styleID;
86 nextStyles &= ~styleID;
93 static void ssa_append_html_tags_for_style_change(
94 uint8_t **dst, StyleSet prevStyles, StyleSet nextStyles )
96 #define APPEND(str) { \
98 while (*src) { *(*dst)++ = *src++; } \
101 // Reverse-order close all previous styles
102 if (prevStyles & UNDERLINE) APPEND("</u>");
103 if (prevStyles & ITALIC) APPEND("</i>");
104 if (prevStyles & BOLD) APPEND("</b>");
106 // Forward-order open all next styles
107 if (nextStyles & BOLD) APPEND("<b>");
108 if (nextStyles & ITALIC) APPEND("<i>");
109 if (nextStyles & UNDERLINE) APPEND("<u>");
114 static hb_buffer_t *ssa_decode_line_to_utf8( uint8_t *in_data, int in_size, int in_sequence );
115 static hb_buffer_t *ssa_decode_line_to_picture( hb_work_object_t * w, uint8_t *in_data, int in_size, int in_sequence );
118 * Decodes a single SSA packet to one or more TEXTSUB or PICTURESUB subtitle packets.
121 * ( Dialogue: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text CR LF ) +
122 * 1 2 3 4 5 6 7 8 9 10
124 static hb_buffer_t *ssa_decode_packet( hb_work_object_t * w, hb_buffer_t *in )
126 // Store NULL after the end of the buffer to make using string processing safe
127 hb_buffer_realloc( in, in->size + 1 );
128 in->data[in->size] = '\0';
130 hb_buffer_t *out_list = NULL;
131 hb_buffer_t **nextPtr = &out_list;
133 const char *EOL = "\r\n";
134 char *curLine, *curLine_parserData;
135 for ( curLine = strtok_r( (char *) in->data, EOL, &curLine_parserData );
137 curLine = strtok_r( NULL, EOL, &curLine_parserData ) )
139 // Skip empty lines and spaces between adjacent CR and LF
140 if (curLine[0] == '\0')
143 // Decode an individual SSA line
145 if ( w->subtitle->config.dest == PASSTHRUSUB ) {
146 out = ssa_decode_line_to_utf8( (uint8_t *) curLine, strlen( curLine ), in->sequence );
150 // We shouldn't be storing the extra NULL character,
151 // but the MP4 muxer expects this, unfortunately.
152 if ( out->size > 0 && out->data[out->size - 1] != '\0' ) {
153 // NOTE: out->size remains unchanged
154 hb_buffer_realloc( out, out->size + 1 );
155 out->data[out->size] = '\0';
158 // If the input packet was non-empty, do not pass through
159 // an empty output packet (even if the subtitle was empty),
160 // as this would be interpreted as an end-of-stream
161 if ( in->size > 0 && out->size == 0 ) {
162 hb_buffer_close(&out);
165 } else if ( w->subtitle->config.dest == RENDERSUB ) {
166 out = ssa_decode_line_to_picture( w, (uint8_t *) curLine, strlen( curLine ), in->sequence );
171 // Append 'out' to 'out_list'
173 nextPtr = &out->next;
176 // For point-to-point encoding, when the start time of the stream
177 // may be offset, the timestamps of the subtitles must be offset as well.
179 // HACK: Here we are making the assumption that, under normal circumstances,
180 // the output display time of the first output packet is equal to the
181 // display time of the input packet.
183 // During point-to-point encoding, the display time of the input
184 // packet will be offset to compensate.
186 // Therefore we offset all of the output packets by a slip amount
187 // such that first output packet's display time aligns with the
188 // input packet's display time. This should give the correct time
189 // when point-to-point encoding is in effect.
190 if (out_list && out_list->start > in->start)
192 int64_t slip = out_list->start - in->start;
208 * Parses the start and stop time from the specified SSA packet.
210 * Returns true if parsing failed; false otherwise.
212 static int parse_timing_from_ssa_packet( char *in_data, int64_t *in_start, int64_t *in_stop )
215 * Parse Start and End fields for timing information
217 int start_hr, start_min, start_sec, start_centi;
218 int end_hr, end_min, end_sec, end_centi;
219 int numPartsRead = sscanf( (char *) in_data, "Dialogue: %*128[^,],"
220 "%d:%d:%d.%d," // Start
221 "%d:%d:%d.%d,", // End
222 &start_hr, &start_min, &start_sec, &start_centi,
223 &end_hr, &end_min, &end_sec, &end_centi );
224 if ( numPartsRead != 8 )
227 *in_start = SSA_2_HB_TIME(start_hr, start_min, start_sec, start_centi);
228 *in_stop = SSA_2_HB_TIME( end_hr, end_min, end_sec, end_centi);
233 static uint8_t *find_field( uint8_t *pos, uint8_t *end, int fieldNum )
241 if ( curFieldID == fieldNum )
250 * Dialogue: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0'
251 * 1 2 3 4 5 6 7 8 9 10
253 static hb_buffer_t *ssa_decode_line_to_utf8( uint8_t *in_data, int in_size, int in_sequence )
255 uint8_t *pos = in_data;
256 uint8_t *end = in_data + in_size;
258 // Parse values for in->start and in->stop
259 int64_t in_start, in_stop;
260 if ( parse_timing_from_ssa_packet( (char *) in_data, &in_start, &in_stop ) )
263 uint8_t *textFieldPos = find_field( pos, end, 10 );
264 if ( textFieldPos == NULL )
267 // Count the number of style overrides in the Text field
268 int numStyleOverrides = 0;
278 int maxOutputSize = (end - textFieldPos) + ((numStyleOverrides + 1) * MAX_OVERHEAD_PER_OVERRIDE);
279 hb_buffer_t *out = hb_buffer_init( maxOutputSize );
284 * The Text field contains plain text marked up with:
286 * (2) '\N' -> newline
287 * (3) curly-brace control codes like '{\k44}' -> HTML tags / strip
289 * Perform the above conversions and copy it to the output packet
291 StyleSet prevStyles = 0;
292 uint8_t *dst = out->data;
296 if ( pos[0] == '\\' && pos[1] == 'n' )
301 else if ( pos[0] == '\\' && pos[1] == 'N' )
306 else if ( pos[0] == '{' )
308 // Parse SSA style overrides and append appropriate HTML style tags
309 StyleSet nextStyles = ssa_parse_style_override( pos, prevStyles );
310 ssa_append_html_tags_for_style_change( &dst, prevStyles, nextStyles );
311 prevStyles = nextStyles;
313 // Skip past SSA control code
314 while ( pos < end && *pos != '}' ) pos++;
315 if ( pos < end && *pos == '}' ) pos++;
319 // Copy raw character
324 // Append closing HTML style tags
325 ssa_append_html_tags_for_style_change( &dst, prevStyles, 0 );
327 // Trim output buffer to the actual amount of data written
328 out->size = dst - out->data;
330 // Copy metadata from the input packet to the output packet
331 out->start = in_start;
333 out->sequence = in_sequence;
338 hb_log( "decssasub: malformed SSA subtitle packet: %.*s\n", in_size, in_data );
344 * Dialogue: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0'
345 * 1 2 3 4 5 6 7 8 9 10
347 * MKV-SSA packet format:
348 * ReadOrder,Marked, Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0'
351 static hb_buffer_t *ssa_decode_line_to_picture( hb_work_object_t * w, uint8_t *in_data, int in_size, int in_sequence )
353 hb_work_private_t * pv = w->private_data;
355 // Parse values for in->start and in->stop
356 int64_t in_start, in_stop;
357 if ( parse_timing_from_ssa_packet( (char *) in_data, &in_start, &in_stop ) )
360 // Convert the SSA packet to MKV-SSA format, which is what libass expects
364 char *layerField = malloc( in_size );
365 int numPartsRead = sscanf( (char *) in_data, "Dialogue: %128[^,],", layerField );
366 if ( numPartsRead != 1 )
369 char *styleToTextFields = (char *) find_field( in_data, in_data + in_size, 4 );
370 if ( styleToTextFields == NULL ) {
375 mkvIn = malloc( in_size + 1 );
377 sprintf(mkvIn, "%d", pv->readOrder++); // ReadOrder: make this up
378 strcat( mkvIn, "," );
379 strcat( mkvIn, layerField );
380 strcat( mkvIn, "," );
381 strcat( mkvIn, (char *) styleToTextFields );
383 mkvInSize = strlen(mkvIn);
388 // Parse MKV-SSA packet
389 ass_process_chunk( pv->ssaTrack, mkvIn, mkvInSize, in_start / 90, (in_stop - in_start) / 90 );
393 // TODO: To support things like karaoke, it won't be sufficient to only generate
394 // new subtitle pictures when there are subtitle packets. Rather, pictures will
395 // need to be generated potentially continuously.
397 // Until "karaoke support" is implemented, make an educated guess about the
398 // timepoint within the subtitle that should be rendered. I guess the midpoint.
399 int64_t renderTime = ( in_start + in_stop ) / 2;
402 ASS_Image *frameList = ass_render_frame( pv->renderer, pv->ssaTrack, renderTime / 90, &changed );
403 if ( !changed || !frameList )
408 for (curFrame = frameList; curFrame; curFrame = curFrame->next)
411 hb_buffer_t *outSubpictureList = NULL;
412 hb_buffer_t **outSubpictureListTailPtr = &outSubpictureList;
414 // Generate a PICTURESUB packet from the frames
416 for (frame = frameList; frame; frame = frame->next) {
417 // Allocate pixmap where drawing will be done
418 uint8_t *rgba = calloc(frame->w * frame->h * 4, 1);
420 unsigned r = (frame->color >> 24) & 0xff;
421 unsigned g = (frame->color >> 16) & 0xff;
422 unsigned b = (frame->color >> 8) & 0xff;
423 unsigned a = (frame->color ) & 0xff;
426 for (y = 0; y < frame->h; y++) {
427 for (x = 0; x < frame->w; x++) {
428 unsigned srcAlphaPrenormalized = frame->bitmap[y*frame->stride + x];
429 unsigned srcAlpha = (255 - a) * srcAlphaPrenormalized / 255;
431 uint8_t *dst = &rgba[(y*frame->w + x) * 4];
432 unsigned oldDstAlpha = dst[3];
434 if (oldDstAlpha == 0) {
441 dst[3] = 255 - ( 255 - dst[3] ) * ( 255 - srcAlpha ) / 255;
443 dst[0] = ( dst[0] * oldDstAlpha * (255-srcAlpha) / 255 + r * srcAlpha ) / dst[3];
444 dst[1] = ( dst[1] * oldDstAlpha * (255-srcAlpha) / 255 + g * srcAlpha ) / dst[3];
445 dst[2] = ( dst[2] * oldDstAlpha * (255-srcAlpha) / 255 + b * srcAlpha ) / dst[3];
451 // Generate output subpicture (in PICTURESUB format)
452 hb_buffer_t *out = hb_buffer_init(frame->w * frame->h * 4);
453 out->x = frame->dst_x;
454 out->y = frame->dst_y;
455 out->width = frame->w;
456 out->height = frame->h;
459 int numPixels = frame->w * frame->h;
460 for (i = 0; i < numPixels; i++) {
461 uint8_t *srcRgba = &rgba[i * 4];
463 uint8_t *dstY = &out->data[(numPixels * 0) + i];
464 uint8_t *dstA = &out->data[(numPixels * 1) + i];
465 uint8_t *dstU = &out->data[(numPixels * 2) + i];
466 uint8_t *dstV = &out->data[(numPixels * 3) + i];
468 int srcYuv = hb_rgb2yuv((srcRgba[0] << 16) | (srcRgba[1] << 8) | (srcRgba[2] << 0));
469 int srcA = srcRgba[3];
471 *dstY = (srcYuv >> 16) & 0xff;
472 *dstU = (srcYuv >> 8 ) & 0xff;
473 *dstV = (srcYuv >> 0 ) & 0xff;
474 *dstA = srcA / 16; // HB's max alpha value is 16
479 *outSubpictureListTailPtr = out;
480 outSubpictureListTailPtr = &out->next_subpicture;
483 // NOTE: The subpicture list is actually considered a single packet by most other code
484 hb_buffer_t *out = outSubpictureList;
486 // Copy metadata from the input packet to the output packet
487 out->start = in_start;
489 out->sequence = in_sequence;
494 hb_log( "decssasub: malformed SSA subtitle packet: %.*s\n", in_size, in_data );
498 static void ssa_log(int level, const char *fmt, va_list args, void *data)
500 if ( level < 5 ) // same as default verbosity when no callback is set
503 if ( vasprintf( &msg, fmt, args ) < 0 )
505 hb_log( "decssasub: could not report libass message\n" );
508 hb_log( "[ass] %s", msg ); // no need for extra '\n' because libass sends it
514 static int decssaInit( hb_work_object_t * w, hb_job_t * job )
516 hb_work_private_t * pv;
518 pv = calloc( 1, sizeof( hb_work_private_t ) );
519 w->private_data = pv;
521 if ( w->subtitle->config.dest == RENDERSUB ) {
522 pv->ssa = ass_library_init();
524 hb_log( "decssasub: libass initialization failed\n" );
528 // Redirect libass output to hb_log
529 ass_set_message_cb( pv->ssa, ssa_log, NULL );
531 // Load embedded fonts
532 hb_list_t * list_attachment = job->title->list_attachment;
534 for ( i = 0; i < hb_list_count(list_attachment); i++ )
536 hb_attachment_t * attachment = hb_list_item( list_attachment, i );
538 if ( attachment->type == FONT_TTF_ATTACH )
548 ass_set_extract_fonts( pv->ssa, 1 );
549 ass_set_style_overrides( pv->ssa, NULL );
551 pv->renderer = ass_renderer_init( pv->ssa );
552 if ( !pv->renderer ) {
553 hb_log( "decssasub: renderer initialization failed\n" );
557 ass_set_use_margins( pv->renderer, 0 );
558 ass_set_hinting( pv->renderer, ASS_HINTING_LIGHT ); // VLC 1.0.4 uses this
559 ass_set_font_scale( pv->renderer, 1.0 );
560 ass_set_line_spacing( pv->renderer, 1.0 );
562 // Setup default font family
564 // SSA v4.00 requires that "Arial" be the default font
565 const char *font = NULL;
566 const char *family = "Arial";
567 // NOTE: This can sometimes block for several *seconds*.
568 // It seems that process_fontdata() for some embedded fonts is slow.
569 ass_set_fonts( pv->renderer, font, family, /*haveFontConfig=*/1, NULL, 1 );
572 pv->ssaTrack = ass_new_track( pv->ssa );
573 if ( !pv->ssaTrack ) {
574 hb_log( "decssasub: ssa track initialization failed\n" );
578 // NOTE: The codec extradata is expected to be in MKV format
579 ass_process_codec_private( pv->ssaTrack,
580 (char *) w->subtitle->extradata, w->subtitle->extradata_size );
582 int originalWidth = job->title->width;
583 int originalHeight = job->title->height;
584 ass_set_frame_size( pv->renderer, originalWidth, originalHeight);
585 ass_set_aspect_ratio( pv->renderer, /*dar=*/1.0, /*sar=*/1.0 );
591 static int decssaWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
592 hb_buffer_t ** buf_out )
594 hb_buffer_t * in = *buf_in;
595 hb_buffer_t * out_list = NULL;
597 #if SSA_VERBOSE_PACKETS
598 printf("\nPACKET(%"PRId64",%"PRId64"): %.*s\n", in->start/90, in->stop/90, in->size, in->data);
601 if ( in->size > 0 ) {
602 out_list = ssa_decode_packet(w, in);
604 out_list = hb_buffer_init( 0 );
607 // Dispose the input packet, as it is no longer needed
608 hb_buffer_close(&in);
615 static void decssaClose( hb_work_object_t * w )
617 hb_work_private_t * pv = w->private_data;
620 ass_free_track( pv->ssaTrack );
622 ass_renderer_done( pv->renderer );
624 ass_library_done( pv->ssa );
626 free( w->private_data );
629 hb_work_object_t hb_decssasub =
632 "SSA Subtitle Decoder",