diff options
author | jstebbins <[email protected]> | 2014-04-13 16:22:16 +0000 |
---|---|---|
committer | jstebbins <[email protected]> | 2014-04-13 16:22:16 +0000 |
commit | 6db1a1e531ad62ba977f4587fb9011b0fd0b3416 (patch) | |
tree | 2c6882e9344b8181a11238835ae163d52e98e49e /libhb | |
parent | e6ca45c979ec69bd1736bc943063083b08ce1914 (diff) |
Convert all text subtitles to ASS subs
Add support for font color to tx3g.
Allow more than one style flag at time in tx3g.
Add positioning support to CC subs
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@6163 b64f7644-9d1e-0410-96f1-a4d463321fa5
Diffstat (limited to 'libhb')
-rw-r--r-- | libhb/common.c | 113 | ||||
-rw-r--r-- | libhb/common.h | 1 | ||||
-rw-r--r-- | libhb/deccc608sub.c | 1562 | ||||
-rw-r--r-- | libhb/deccc608sub.h | 48 | ||||
-rw-r--r-- | libhb/decsrtsub.c | 182 | ||||
-rw-r--r-- | libhb/decsrtsub.h | 16 | ||||
-rw-r--r-- | libhb/decssasub.c | 462 | ||||
-rw-r--r-- | libhb/decssasub.h | 35 | ||||
-rw-r--r-- | libhb/dectx3gsub.c | 233 | ||||
-rw-r--r-- | libhb/decutf8sub.c | 31 | ||||
-rw-r--r-- | libhb/muxavformat.c | 66 | ||||
-rw-r--r-- | libhb/muxcommon.c | 346 | ||||
-rw-r--r-- | libhb/muxmkv.c | 14 | ||||
-rw-r--r-- | libhb/rendersub.c | 203 |
14 files changed, 1377 insertions, 1935 deletions
diff --git a/libhb/common.c b/libhb/common.c index b689facd4..8e9c02f22 100644 --- a/libhb/common.c +++ b/libhb/common.c @@ -824,7 +824,7 @@ int hb_audio_bitrate_get_default(uint32_t codec, int samplerate, int mixdown) case HB_ACODEC_FFFLAC: case HB_ACODEC_FFFLAC24: goto fail; - + // 96, 224, 640 Kbps case HB_ACODEC_AC3: bitrate = (nchannels * 128) - (32 * (nchannels < 5)); @@ -1323,14 +1323,14 @@ void hb_audio_compression_get_limits(uint32_t codec, float *low, float *high, *high = 12.; *low = 0.; break; - + case HB_ACODEC_LAME: *direction = 1; *granularity = 1.; *high = 9.; *low = 0.; break; - + default: *direction = 0; *granularity = 1.; @@ -2798,9 +2798,9 @@ void hb_error( char * log, ... ) { hb_unlock( mutex ); return; - } + } } - + /* * A new error, or the same one more than 10sec since the last one * did we have any of the same counted up? @@ -2808,7 +2808,7 @@ void hb_error( char * log, ... ) if( last_error_count > 0 ) { /* - * Print out the last error to ensure context for the last + * Print out the last error to ensure context for the last * repeated message. */ if( error_handler ) @@ -2817,16 +2817,16 @@ void hb_error( char * log, ... ) } else { hb_log( "%s", last_string ); } - + if( last_error_count > 1 ) { /* * Only print out the repeat message for more than 2 of the * same, since we just printed out two of them already. */ - snprintf( rep_string, 180, "Last error repeated %d times", + snprintf( rep_string, 180, "Last error repeated %d times", last_error_count - 1 ); - + if( error_handler ) { error_handler( rep_string ); @@ -2834,7 +2834,7 @@ void hb_error( char * log, ... ) hb_log( "%s", rep_string ); } } - + last_error_count = 0; } @@ -2936,7 +2936,7 @@ void hb_title_close( hb_title_t ** _t ) hb_subtitle_close( &subtitle ); } hb_list_close( &t->list_subtitle ); - + while( ( attachment = hb_list_item( t->list_attachment, 0 ) ) ) { hb_list_rem( t->list_attachment, attachment ); @@ -3565,7 +3565,7 @@ int hb_audio_add(const hb_job_t * job, const hb_audio_config_t * audiocfg) /* Set the job's "in track" to the value passed in audiocfg. * HandBrakeCLI assumes this value is preserved in the jobs - * audio list, but in.track in the title's audio list is not + * audio list, but in.track in the title's audio list is not * required to be the same. */ audio->config.in.track = audiocfg->in.track; @@ -3687,6 +3687,39 @@ void hb_subtitle_close( hb_subtitle_t **sub ) ********************************************************************** * *********************************************************************/ +int hb_subtitle_add_ssa_header(hb_subtitle_t *subtitle, int w, int h) +{ + // Free any pre-existing extradata + free(subtitle->extradata); + + int fs = h * .066; + + // SRT subtitles are represented internally as SSA + // Create an SSA header + const char * ssa_header = + "[Script Info]\r\n" + "ScriptType: v4.00+\r\n" + "Collisions: Normal\r\n" + "PlayResX: %d\r\n" + "PlayResY: %d\r\n" + "Timer: 100.0\r\n" + "WrapStyle: 0\r\n" + "\r\n" + "[V4+ Styles]\r\n" + "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\r\n" + "Style: Default,Arial,%d,&H00FFFFFF,&H00FFFFFF,&H000F0F0F,&H000F0F0F,0,0,0,0,100,100,0,0.00,1,2,3,2,20,20,20,0\r\n"; + + subtitle->extradata = (uint8_t*)hb_strdup_printf(ssa_header, w, h, fs); + if (subtitle->extradata == NULL) + { + hb_error("hb_subtitle_add_ssa_header: malloc failed"); + return 0; + } + subtitle->extradata_size = strlen((char*)subtitle->extradata) + 1; + + return 1; +} + int hb_subtitle_add(const hb_job_t * job, const hb_subtitle_config_t * subtitlecfg, int track) { hb_title_t *title = job->title; @@ -3698,41 +3731,45 @@ int hb_subtitle_add(const hb_job_t * job, const hb_subtitle_config_t * subtitlec /* We fail! */ return 0; } + subtitle->config = *subtitlecfg; subtitle->out_track = hb_list_count(job->list_subtitle) + 1; hb_list_add(job->list_subtitle, subtitle); return 1; } -int hb_srt_add( const hb_job_t * job, - const hb_subtitle_config_t * subtitlecfg, +int hb_srt_add( const hb_job_t * job, + const hb_subtitle_config_t * subtitlecfg, const char *lang ) { hb_subtitle_t *subtitle; iso639_lang_t *language = NULL; - int retval = 0; subtitle = calloc( 1, sizeof( *subtitle ) ); - + if (subtitle == NULL) + { + hb_error("hb_srt_add: malloc failed"); + return 0; + } + subtitle->id = (hb_list_count(job->list_subtitle) << 8) | 0xFF; subtitle->format = TEXTSUB; subtitle->source = SRTSUB; subtitle->codec = WORK_DECSRTSUB; - language = lang_for_code2( lang ); - - if( language ) + language = lang_for_code2(lang); + if (language == NULL) { + hb_log("hb_srt_add: unknown language code (%s)", lang); + language = lang_for_code2("und"); + } + strcpy(subtitle->lang, language->eng_name); + strcpy(subtitle->iso639_2, language->iso639_2); - strcpy( subtitle->lang, language->eng_name ); - strncpy( subtitle->iso639_2, lang, 4 ); - - subtitle->config = *subtitlecfg; + subtitle->config = *subtitlecfg; + hb_list_add(job->list_subtitle, subtitle); - hb_list_add(job->list_subtitle, subtitle); - retval = 1; - } - return retval; + return 1; } int hb_subtitle_can_force( int source ) @@ -4062,7 +4099,7 @@ char * hb_strdup_printf( const char * fmt, ... ) if ( str == NULL ) return NULL; - while (1) + while (1) { /* Try to print in the allocated space. */ va_start( ap, fmt ); @@ -4204,15 +4241,15 @@ int hb_yuv2rgb(int yuv) r = 1.164 * (y - 16) + 1.596 * (Cr - 128); g = 1.164 * (y - 16) - 0.392 * (Cb - 128) - 0.813 * (Cr - 128); b = 1.164 * (y - 16) + 2.017 * (Cb - 128); - + r = (r < 0) ? 0 : r; g = (g < 0) ? 0 : g; b = (b < 0) ? 0 : b; - + r = (r > 255) ? 255 : r; g = (g > 255) ? 255 : g; b = (b > 255) ? 255 : b; - + return (r << 16) | (g << 8) | b; } @@ -4230,7 +4267,7 @@ int hb_rgb2yuv(int rgb) { double r, g, b; int y, Cr, Cb; - + r = (rgb >> 16) & 0xff; g = (rgb >> 8) & 0xff; b = (rgb ) & 0xff; @@ -4238,15 +4275,15 @@ int hb_rgb2yuv(int rgb) y = 16. + ( 0.257 * r) + (0.504 * g) + (0.098 * b); Cb = 128. + (-0.148 * r) - (0.291 * g) + (0.439 * b); Cr = 128. + ( 0.439 * r) - (0.368 * g) - (0.071 * b); - + y = (y < 0) ? 0 : y; Cb = (Cb < 0) ? 0 : Cb; Cr = (Cr < 0) ? 0 : Cr; - + y = (y > 255) ? 255 : y; Cb = (Cb > 255) ? 255 : Cb; Cr = (Cr > 255) ? 255 : Cr; - + return (y << 16) | (Cr << 8) | Cb; } @@ -4321,10 +4358,10 @@ void hb_hexdump( hb_debug_level_t level, const char * label, const uint8_t * dat int hb_gui_use_hwd_flag = 0; int hb_use_dxva( hb_title_t * title ) { - return ( (title->video_codec_param == AV_CODEC_ID_MPEG2VIDEO + return ( (title->video_codec_param == AV_CODEC_ID_MPEG2VIDEO || title->video_codec_param == AV_CODEC_ID_H264 - || title->video_codec_param == AV_CODEC_ID_VC1 - || title->video_codec_param == AV_CODEC_ID_WMV3 + || title->video_codec_param == AV_CODEC_ID_VC1 + || title->video_codec_param == AV_CODEC_ID_WMV3 || title->video_codec_param == AV_CODEC_ID_MPEG4 ) && title->opaque_priv ); } diff --git a/libhb/common.h b/libhb/common.h index 4ccac8402..97ac820df 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -151,6 +151,7 @@ void hb_audio_config_init(hb_audio_config_t * audiocfg); int hb_audio_add(const hb_job_t * job, const hb_audio_config_t * audiocfg); hb_audio_config_t * hb_list_audio_config_item(hb_list_t * list, int i); +int hb_subtitle_add_ssa_header(hb_subtitle_t *subtitle, int width, int height); 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 ); diff --git a/libhb/deccc608sub.c b/libhb/deccc608sub.c index 85b43efab..5f4066b67 100644 --- a/libhb/deccc608sub.c +++ b/libhb/deccc608sub.c @@ -7,15 +7,12 @@ #include "hb.h" #include "deccc608sub.h" +#define SSA_PREAMBLE_LEN 24 /* * ccextractor static configuration variables. */ static int debug_608 = 0; -static int trim_subs = 1; -static int nofontcolor = 0; -static enum encoding_type encoding = ENC_UTF_8; static int cc_channel = 1; -static int sentence_cap = 0; static int subs_delay = 0; static int norollup = 0; static int direct_rollup = 0; @@ -23,28 +20,21 @@ static int direct_rollup = 0; /* * Get the time of the last buffer that we have received. */ -static int64_t get_fts(struct s_write *wb) +static int64_t get_last_pts(struct s_write *wb) { return wb->last_pts; } #define fatal(N, ...) // N -int rowdata[] = {11,-1,1,2,3,4,12,13,14,15,5,6,7,8,9,10}; +int rowdata[] = {11,-1,1,2,3,4,12,13,14,15,5,6,7,8,9,10}; // Relationship between the first PAC byte and the row number // The following enc_buffer is not used at the moment, if it does get used // we need to bring it into the swrite struct. Same for "str". #define INITIAL_ENC_BUFFER_CAPACITY 2048 -unsigned char str[2048]; // Another generic general purpose buffer - -#define GUARANTEE(wb, length) if (length>wb->enc_buffer_capacity) \ -{wb->enc_buffer_capacity*=2; wb->enc_buffer=(unsigned char*) realloc (wb->enc_buffer, wb->enc_buffer_capacity); \ - if (wb->enc_buffer==NULL) { fatal (EXIT_NOT_ENOUGH_MEMORY, "Not enough memory, bailing out\n"); } \ -} - -const unsigned char pac2_attribs[][3]= // Color, font, ident +static const unsigned char pac2_attribs[][3]= // Color, font, ident { {COL_WHITE, FONT_REGULAR, 0}, // 0x40 || 0x60 {COL_WHITE, FONT_UNDERLINED, 0}, // 0x41 || 0x61 @@ -80,17 +70,10 @@ const unsigned char pac2_attribs[][3]= // Color, font, ident {COL_WHITE, FONT_UNDERLINED, 28} // 0x5f || 0x7f }; -// Preencoded strings -unsigned char encoded_crlf[16]; -unsigned int encoded_crlf_length; -unsigned char encoded_br[16]; -unsigned int encoded_br_length; - // Default color -unsigned char usercolor_rgb[8]=""; -enum color_code default_color=COL_WHITE; +static enum color_code default_color=COL_WHITE; -const char *command_type[] = +static const char *command_type[] = { "Unknown", "EDM - EraseDisplayedMemory", @@ -108,7 +91,7 @@ const char *command_type[] = "RTD - Resume Text Display" }; -const char *font_text[]= +static const char *font_text[]= { "regular", "italics", @@ -116,24 +99,19 @@ const char *font_text[]= "underlined italics" }; -const char *cc_modes_text[]= -{ - "Pop-Up captions" -}; - -const char *color_text[][2]= +static const char *color_text[][2]= { - {"white",""}, - {"green","<font color=\"#00ff00\">"}, - {"blue","<font color=\"#0000ff\">"}, - {"cyan","<font color=\"#00ffff\">"}, - {"red","<font color=\"#ff0000\">"}, - {"yellow","<font color=\"#ffff00\">"}, - {"magenta","<font color=\"#ff00ff\">"}, - {"userdefined","<font color=\""} + {"white", "&HFFFFFF&"}, + {"green", "&H00FF00&"}, + {"blue", "&HFF0000&"}, + {"cyan", "&HFFFF00&"}, + {"red", "&H0000FF&"}, + {"yellow", "&H00FFFF&"}, + {"magenta", "&HFF00FF&"}, + {"userdefined", "&HFFFFFF&"} }; -int general_608_init (struct s_write *wb) +static int general_608_init (struct s_write *wb) { if( !wb->enc_buffer ) { @@ -167,7 +145,7 @@ int general_608_init (struct s_write *wb) * parallel encodes to fail - to be honest they will be stuffed anyway since * the CC's may be overwriting the buffers. */ -void general_608_close (struct s_write *wb) +static void general_608_close (struct s_write *wb) { if( wb->enc_buffer ) { free(wb->enc_buffer); @@ -186,1076 +164,501 @@ void general_608_close (struct s_write *wb) #include <ctype.h> -void get_char_in_latin_1 (unsigned char *buffer, unsigned char c) +// Returns number of bytes used +static int get_char_in_utf8(unsigned char *buffer, unsigned char c) { - unsigned char c1='?'; - if (c<0x80) - { - // Regular line-21 character set, mostly ASCII except these exceptions - switch (c) - { - case 0x2a: // lowercase a, acute accent - c1=0xe1; - break; - case 0x5c: // lowercase e, acute accent - c1=0xe9; - break; - case 0x5e: // lowercase i, acute accent - c1=0xed; - break; - case 0x5f: // lowercase o, acute accent - c1=0xf3; - break; - case 0x60: // lowercase u, acute accent - c1=0xfa; - break; - case 0x7b: // lowercase c with cedilla - c1=0xe7; - break; - case 0x7c: // division symbol - c1=0xf7; - break; - case 0x7d: // uppercase N tilde - c1=0xd1; - break; - case 0x7e: // lowercase n tilde - c1=0xf1; - break; - default: - c1=c; - break; - } - *buffer=c1; - return; - } - switch (c) - { - // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS - // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F - case 0x80: // Registered symbol (R) - c1=0xae; - break; - case 0x81: // degree sign - c1=0xb0; - break; - case 0x82: // 1/2 symbol - c1=0xbd; - break; - case 0x83: // Inverted (open) question mark - c1=0xbf; - break; - case 0x84: // Trademark symbol (TM) - Does not exist in Latin 1 - break; - case 0x85: // Cents symbol - c1=0xa2; - break; - case 0x86: // Pounds sterling - c1=0xa3; - break; - case 0x87: // Music note - Not in latin 1, so we use 'pilcrow' - c1=0xb6; - break; - case 0x88: // lowercase a, grave accent - c1=0xe0; - break; - case 0x89: // transparent space, we make it regular - c1=0x20; - break; - case 0x8a: // lowercase e, grave accent - c1=0xe8; - break; - case 0x8b: // lowercase a, circumflex accent - c1=0xe2; - break; - case 0x8c: // lowercase e, circumflex accent - c1=0xea; - break; - case 0x8d: // lowercase i, circumflex accent - c1=0xee; - break; - case 0x8e: // lowercase o, circumflex accent - c1=0xf4; - break; - case 0x8f: // lowercase u, circumflex accent - c1=0xfb; - break; - // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS - // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F - case 0x90: // capital letter A with acute - c1=0xc1; - break; - case 0x91: // capital letter E with acute - c1=0xc9; - break; - case 0x92: // capital letter O with acute - c1=0xd3; - break; - case 0x93: // capital letter U with acute - c1=0xda; - break; - case 0x94: // capital letter U with diaresis - c1=0xdc; - break; - case 0x95: // lowercase letter U with diaeresis - c1=0xfc; - break; - case 0x96: // apostrophe - c1=0x27; - break; - case 0x97: // inverted exclamation mark - c1=0xa1; - break; - case 0x98: // asterisk - c1=0x2a; - break; - case 0x99: // apostrophe (yes, duped). See CCADI source code. - c1=0x27; - break; - case 0x9a: // hyphen-minus - c1=0x2d; - break; - case 0x9b: // copyright sign - c1=0xa9; - break; - case 0x9c: // Service Mark - not available in latin 1 - break; - case 0x9d: // Full stop (.) - c1=0x2e; - break; - case 0x9e: // Quoatation mark - c1=0x22; - break; - case 0x9f: // Quoatation mark - c1=0x22; - break; - case 0xa0: // uppercase A, grave accent - c1=0xc0; - break; - case 0xa1: // uppercase A, circumflex - c1=0xc2; - break; - case 0xa2: // uppercase C with cedilla - c1=0xc7; - break; - case 0xa3: // uppercase E, grave accent - c1=0xc8; - break; - case 0xa4: // uppercase E, circumflex - c1=0xca; - break; - case 0xa5: // capital letter E with diaresis - c1=0xcb; - break; - case 0xa6: // lowercase letter e with diaresis - c1=0xeb; - break; - case 0xa7: // uppercase I, circumflex - c1=0xce; - break; - case 0xa8: // uppercase I, with diaresis - c1=0xcf; - break; - case 0xa9: // lowercase i, with diaresis - c1=0xef; - break; - case 0xaa: // uppercase O, circumflex - c1=0xd4; - break; - case 0xab: // uppercase U, grave accent - c1=0xd9; - break; - case 0xac: // lowercase u, grave accent - c1=0xf9; - break; - case 0xad: // uppercase U, circumflex - c1=0xdb; - break; - case 0xae: // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - c1=0xab; - break; - case 0xaf: // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - c1=0xbb; - break; - // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS - // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F - case 0xb0: // Uppercase A, tilde - c1=0xc3; - break; - case 0xb1: // Lowercase a, tilde - c1=0xe3; - break; - case 0xb2: // Uppercase I, acute accent - c1=0xcd; - break; - case 0xb3: // Uppercase I, grave accent - c1=0xcc; - break; - case 0xb4: // Lowercase i, grave accent - c1=0xec; - break; - case 0xb5: // Uppercase O, grave accent - c1=0xd2; - break; - case 0xb6: // Lowercase o, grave accent - c1=0xf2; - break; - case 0xb7: // Uppercase O, tilde - c1=0xd5; - break; - case 0xb8: // Lowercase o, tilde - c1=0xf5; - break; - case 0xb9: // Open curly brace - c1=0x7b; - break; - case 0xba: // Closing curly brace - c1=0x7d; - break; - case 0xbb: // Backslash - c1=0x5c; - break; - case 0xbc: // Caret - c1=0x5e; - break; - case 0xbd: // Underscore - c1=0x5f; - break; - case 0xbe: // Pipe (broken bar) - c1=0xa6; - break; - case 0xbf: // Tilde - c1=0x7e; - break; - case 0xc0: // Uppercase A, umlaut - c1=0xc4; - break; - case 0xc1: // Lowercase A, umlaut - c1=0xe3; - break; - case 0xc2: // Uppercase O, umlaut - c1=0xd6; - break; - case 0xc3: // Lowercase o, umlaut - c1=0xf6; - break; - case 0xc4: // Esszett (sharp S) - c1=0xdf; - break; - case 0xc5: // Yen symbol - c1=0xa5; - break; - case 0xc6: // Currency symbol - c1=0xa4; - break; - case 0xc7: // Vertical bar - c1=0x7c; - break; - case 0xc8: // Uppercase A, ring - c1=0xc5; - break; - case 0xc9: // Lowercase A, ring - c1=0xe5; - break; - case 0xca: // Uppercase O, slash - c1=0xd8; - break; - case 0xcb: // Lowercase o, slash - c1=0xf8; - break; - case 0xcc: // Upper left corner - case 0xcd: // Upper right corner - case 0xce: // Lower left corner - case 0xcf: // Lower right corner - default: // For those that don't have representation - *buffer='?'; // I'll do it eventually, I promise - break; // This are weird chars anyway - } - *buffer=c1; -} - -void get_char_in_unicode (unsigned char *buffer, unsigned char c) -{ - unsigned char c1,c2; - switch (c) - { - case 0x84: // Trademark symbol (TM) - c2=0x21; - c1=0x22; - break; - case 0x87: // Music note - c2=0x26; - c1=0x6a; - break; - case 0x9c: // Service Mark - c2=0x21; - c1=0x20; - break; - case 0xcc: // Upper left corner - c2=0x23; - c1=0x1c; - break; - case 0xcd: // Upper right corner - c2=0x23; - c1=0x1d; - break; - case 0xce: // Lower left corner - c2=0x23; - c1=0x1e; - break; - case 0xcf: // Lower right corner - c2=0x23; - c1=0x1f; - break; - default: // Everything else, same as latin-1 followed by 00 - get_char_in_latin_1 (&c1,c); - c2=0; - break; - } - *buffer=c1; - *(buffer+1)=c2; -} - -int get_char_in_utf_8 (unsigned char *buffer, unsigned char c) // Returns number of bytes used -{ - if (c==0x00) + if (c == 0x00) return 0; - if (c<0x80) // Regular line-21 character set, mostly ASCII except these exceptions + + // Regular line-21 character set, mostly ASCII except these exceptions + if (c < 0x80) { switch (c) { case 0x2a: // lowercase a, acute accent - *buffer=0xc3; - *(buffer+1)=0xa1; + *buffer = 0xc3; + *(buffer+1) = 0xa1; return 2; case 0x5c: // lowercase e, acute accent - *buffer=0xc3; - *(buffer+1)=0xa9; + *buffer = 0xc3; + *(buffer+1) = 0xa9; return 2; case 0x5e: // lowercase i, acute accent - *buffer=0xc3; - *(buffer+1)=0xad; + *buffer = 0xc3; + *(buffer+1) = 0xad; return 2; case 0x5f: // lowercase o, acute accent - *buffer=0xc3; - *(buffer+1)=0xb3; + *buffer = 0xc3; + *(buffer+1) = 0xb3; return 2; case 0x60: // lowercase u, acute accent - *buffer=0xc3; - *(buffer+1)=0xba; + *buffer = 0xc3; + *(buffer+1) = 0xba; return 2; case 0x7b: // lowercase c with cedilla - *buffer=0xc3; - *(buffer+1)=0xa7; + *buffer = 0xc3; + *(buffer+1) = 0xa7; return 2; case 0x7c: // division symbol - *buffer=0xc3; - *(buffer+1)=0xb7; + *buffer = 0xc3; + *(buffer+1) = 0xb7; return 2; case 0x7d: // uppercase N tilde - *buffer=0xc3; - *(buffer+1)=0x91; + *buffer = 0xc3; + *(buffer+1) = 0x91; return 2; case 0x7e: // lowercase n tilde - *buffer=0xc3; - *(buffer+1)=0xb1; + *buffer = 0xc3; + *(buffer+1) = 0xb1; return 2; default: - *buffer=c; + *buffer = c; return 1; } } switch (c) { // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS - // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F + // THAT COME FROM HI BYTE = 0x11 AND LOW BETWEEN 0x30 AND 0x3F case 0x80: // Registered symbol (R) - *buffer=0xc2; - *(buffer+1)=0xae; + *buffer = 0xc2; + *(buffer+1) = 0xae; return 2; case 0x81: // degree sign - *buffer=0xc2; - *(buffer+1)=0xb0; + *buffer = 0xc2; + *(buffer+1) = 0xb0; return 2; case 0x82: // 1/2 symbol - *buffer=0xc2; - *(buffer+1)=0xbd; + *buffer = 0xc2; + *(buffer+1) = 0xbd; return 2; case 0x83: // Inverted (open) question mark - *buffer=0xc2; - *(buffer+1)=0xbf; + *buffer = 0xc2; + *(buffer+1) = 0xbf; return 2; case 0x84: // Trademark symbol (TM) - *buffer=0xe2; - *(buffer+1)=0x84; - *(buffer+2)=0xa2; + *buffer = 0xe2; + *(buffer+1) = 0x84; + *(buffer+2) = 0xa2; return 3; case 0x85: // Cents symbol - *buffer=0xc2; - *(buffer+1)=0xa2; + *buffer = 0xc2; + *(buffer+1) = 0xa2; return 2; case 0x86: // Pounds sterling - *buffer=0xc2; - *(buffer+1)=0xa3; + *buffer = 0xc2; + *(buffer+1) = 0xa3; return 2; case 0x87: // Music note - *buffer=0xe2; - *(buffer+1)=0x99; - *(buffer+2)=0xaa; + *buffer = 0xe2; + *(buffer+1) = 0x99; + *(buffer+2) = 0xaa; return 3; case 0x88: // lowercase a, grave accent - *buffer=0xc3; - *(buffer+1)=0xa0; + *buffer = 0xc3; + *(buffer+1) = 0xa0; return 2; case 0x89: // transparent space, we make it regular - *buffer=0x20; + *buffer = 0x20; return 1; case 0x8a: // lowercase e, grave accent - *buffer=0xc3; - *(buffer+1)=0xa8; + *buffer = 0xc3; + *(buffer+1) = 0xa8; return 2; case 0x8b: // lowercase a, circumflex accent - *buffer=0xc3; - *(buffer+1)=0xa2; + *buffer = 0xc3; + *(buffer+1) = 0xa2; return 2; case 0x8c: // lowercase e, circumflex accent - *buffer=0xc3; - *(buffer+1)=0xaa; + *buffer = 0xc3; + *(buffer+1) = 0xaa; return 2; case 0x8d: // lowercase i, circumflex accent - *buffer=0xc3; - *(buffer+1)=0xae; + *buffer = 0xc3; + *(buffer+1) = 0xae; return 2; case 0x8e: // lowercase o, circumflex accent - *buffer=0xc3; - *(buffer+1)=0xb4; + *buffer = 0xc3; + *(buffer+1) = 0xb4; return 2; case 0x8f: // lowercase u, circumflex accent - *buffer=0xc3; - *(buffer+1)=0xbb; + *buffer = 0xc3; + *(buffer+1) = 0xbb; return 2; // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS - // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F + // THAT COME FROM HI BYTE = 0x12 AND LOW BETWEEN 0x20 AND 0x3F case 0x90: // capital letter A with acute - *buffer=0xc3; - *(buffer+1)=0x81; + *buffer = 0xc3; + *(buffer+1) = 0x81; return 2; case 0x91: // capital letter E with acute - *buffer=0xc3; - *(buffer+1)=0x89; + *buffer = 0xc3; + *(buffer+1) = 0x89; return 2; case 0x92: // capital letter O with acute - *buffer=0xc3; - *(buffer+1)=0x93; + *buffer = 0xc3; + *(buffer+1) = 0x93; return 2; case 0x93: // capital letter U with acute - *buffer=0xc3; - *(buffer+1)=0x9a; + *buffer = 0xc3; + *(buffer+1) = 0x9a; return 2; case 0x94: // capital letter U with diaresis - *buffer=0xc3; - *(buffer+1)=0x9c; + *buffer = 0xc3; + *(buffer+1) = 0x9c; return 2; case 0x95: // lowercase letter U with diaeresis - *buffer=0xc3; - *(buffer+1)=0xbc; + *buffer = 0xc3; + *(buffer+1) = 0xbc; return 2; case 0x96: // apostrophe - *buffer=0x27; + *buffer = 0x27; return 1; case 0x97: // inverted exclamation mark - *buffer=0xc2; - *(buffer+1)=0xa1; + *buffer = 0xc2; + *(buffer+1) = 0xa1; return 2; case 0x98: // asterisk - *buffer=0x2a; + *buffer = 0x2a; return 1; case 0x99: // apostrophe (yes, duped). See CCADI source code. - *buffer=0x27; + *buffer = 0x27; return 1; case 0x9a: // hyphen-minus - *buffer=0x2d; + *buffer = 0x2d; return 1; case 0x9b: // copyright sign - *buffer=0xc2; - *(buffer+1)=0xa9; + *buffer = 0xc2; + *(buffer+1) = 0xa9; return 2; case 0x9c: // Service mark - *buffer=0xe2; - *(buffer+1)=0x84; - *(buffer+2)=0xa0; + *buffer = 0xe2; + *(buffer+1) = 0x84; + *(buffer+2) = 0xa0; return 3; case 0x9d: // Full stop (.) - *buffer=0x2e; + *buffer = 0x2e; return 1; case 0x9e: // Quoatation mark - *buffer=0x22; + *buffer = 0x22; return 1; case 0x9f: // Quoatation mark - *buffer=0x22; + *buffer = 0x22; return 1; case 0xa0: // uppercase A, grave accent - *buffer=0xc3; - *(buffer+1)=0x80; + *buffer = 0xc3; + *(buffer+1) = 0x80; return 2; case 0xa1: // uppercase A, circumflex - *buffer=0xc3; - *(buffer+1)=0x82; + *buffer = 0xc3; + *(buffer+1) = 0x82; return 2; case 0xa2: // uppercase C with cedilla - *buffer=0xc3; - *(buffer+1)=0x87; + *buffer = 0xc3; + *(buffer+1) = 0x87; return 2; case 0xa3: // uppercase E, grave accent - *buffer=0xc3; - *(buffer+1)=0x88; + *buffer = 0xc3; + *(buffer+1) = 0x88; return 2; case 0xa4: // uppercase E, circumflex - *buffer=0xc3; - *(buffer+1)=0x8a; + *buffer = 0xc3; + *(buffer+1) = 0x8a; return 2; case 0xa5: // capital letter E with diaresis - *buffer=0xc3; - *(buffer+1)=0x8b; + *buffer = 0xc3; + *(buffer+1) = 0x8b; return 2; case 0xa6: // lowercase letter e with diaresis - *buffer=0xc3; - *(buffer+1)=0xab; + *buffer = 0xc3; + *(buffer+1) = 0xab; return 2; case 0xa7: // uppercase I, circumflex - *buffer=0xc3; - *(buffer+1)=0x8e; + *buffer = 0xc3; + *(buffer+1) = 0x8e; return 2; case 0xa8: // uppercase I, with diaresis - *buffer=0xc3; - *(buffer+1)=0x8f; + *buffer = 0xc3; + *(buffer+1) = 0x8f; return 2; case 0xa9: // lowercase i, with diaresis - *buffer=0xc3; - *(buffer+1)=0xaf; + *buffer = 0xc3; + *(buffer+1) = 0xaf; return 2; case 0xaa: // uppercase O, circumflex - *buffer=0xc3; - *(buffer+1)=0x94; + *buffer = 0xc3; + *(buffer+1) = 0x94; return 2; case 0xab: // uppercase U, grave accent - *buffer=0xc3; - *(buffer+1)=0x99; + *buffer = 0xc3; + *(buffer+1) = 0x99; return 2; case 0xac: // lowercase u, grave accent - *buffer=0xc3; - *(buffer+1)=0xb9; + *buffer = 0xc3; + *(buffer+1) = 0xb9; return 2; case 0xad: // uppercase U, circumflex - *buffer=0xc3; - *(buffer+1)=0x9b; + *buffer = 0xc3; + *(buffer+1) = 0x9b; return 2; case 0xae: // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - *buffer=0xc2; - *(buffer+1)=0xab; + *buffer = 0xc2; + *(buffer+1) = 0xab; return 2; case 0xaf: // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - *buffer=0xc2; - *(buffer+1)=0xbb; + *buffer = 0xc2; + *(buffer+1) = 0xbb; return 2; // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS - // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F + // THAT COME FROM HI BYTE = 0x13 AND LOW BETWEEN 0x20 AND 0x3F case 0xb0: // Uppercase A, tilde - *buffer=0xc3; - *(buffer+1)=0x83; + *buffer = 0xc3; + *(buffer+1) = 0x83; return 2; case 0xb1: // Lowercase a, tilde - *buffer=0xc3; - *(buffer+1)=0xa3; + *buffer = 0xc3; + *(buffer+1) = 0xa3; return 2; case 0xb2: // Uppercase I, acute accent - *buffer=0xc3; - *(buffer+1)=0x8d; + *buffer = 0xc3; + *(buffer+1) = 0x8d; return 2; case 0xb3: // Uppercase I, grave accent - *buffer=0xc3; - *(buffer+1)=0x8c; + *buffer = 0xc3; + *(buffer+1) = 0x8c; return 2; case 0xb4: // Lowercase i, grave accent - *buffer=0xc3; - *(buffer+1)=0xac; + *buffer = 0xc3; + *(buffer+1) = 0xac; return 2; case 0xb5: // Uppercase O, grave accent - *buffer=0xc3; - *(buffer+1)=0x92; + *buffer = 0xc3; + *(buffer+1) = 0x92; return 2; case 0xb6: // Lowercase o, grave accent - *buffer=0xc3; - *(buffer+1)=0xb2; + *buffer = 0xc3; + *(buffer+1) = 0xb2; return 2; case 0xb7: // Uppercase O, tilde - *buffer=0xc3; - *(buffer+1)=0x95; + *buffer = 0xc3; + *(buffer+1) = 0x95; return 2; case 0xb8: // Lowercase o, tilde - *buffer=0xc3; - *(buffer+1)=0xb5; + *buffer = 0xc3; + *(buffer+1) = 0xb5; return 2; case 0xb9: // Open curly brace - *buffer=0x7b; + *buffer = 0x7b; return 1; case 0xba: // Closing curly brace - *buffer=0x7d; + *buffer = 0x7d; return 1; case 0xbb: // Backslash - *buffer=0x5c; + *buffer = 0x5c; return 1; case 0xbc: // Caret - *buffer=0x5e; + *buffer = 0x5e; return 1; case 0xbd: // Underscore - *buffer=0x5f; + *buffer = 0x5f; return 1; case 0xbe: // Pipe (broken bar) - *buffer=0xc2; - *(buffer+1)=0xa6; + *buffer = 0xc2; + *(buffer+1) = 0xa6; return 1; case 0xbf: // Tilde - *buffer=0x7e; // Not sure + *buffer = 0x7e; // Not sure return 1; case 0xc0: // Uppercase A, umlaut - *buffer=0xc3; - *(buffer+1)=0x84; + *buffer = 0xc3; + *(buffer+1) = 0x84; return 2; case 0xc1: // Lowercase A, umlaut - *buffer=0xc3; - *(buffer+1)=0xa4; + *buffer = 0xc3; + *(buffer+1) = 0xa4; return 2; case 0xc2: // Uppercase O, umlaut - *buffer=0xc3; - *(buffer+1)=0x96; + *buffer = 0xc3; + *(buffer+1) = 0x96; return 2; case 0xc3: // Lowercase o, umlaut - *buffer=0xc3; - *(buffer+1)=0xb6; + *buffer = 0xc3; + *(buffer+1) = 0xb6; return 2; case 0xc4: // Esszett (sharp S) - *buffer=0xc3; - *(buffer+1)=0x9f; + *buffer = 0xc3; + *(buffer+1) = 0x9f; return 2; case 0xc5: // Yen symbol - *buffer=0xc2; - *(buffer+1)=0xa5; + *buffer = 0xc2; + *(buffer+1) = 0xa5; return 2; case 0xc6: // Currency symbol - *buffer=0xc2; - *(buffer+1)=0xa4; + *buffer = 0xc2; + *(buffer+1) = 0xa4; return 2; case 0xc7: // Vertical bar - *buffer=0x7c; + *buffer = 0x7c; return 1; case 0xc8: // Uppercase A, ring - *buffer=0xc3; - *(buffer+1)=0x85; + *buffer = 0xc3; + *(buffer+1) = 0x85; return 2; case 0xc9: // Lowercase A, ring - *buffer=0xc3; - *(buffer+1)=0xa5; + *buffer = 0xc3; + *(buffer+1) = 0xa5; return 2; case 0xca: // Uppercase O, slash - *buffer=0xc3; - *(buffer+1)=0x98; + *buffer = 0xc3; + *(buffer+1) = 0x98; return 2; case 0xcb: // Lowercase o, slash - *buffer=0xc3; - *(buffer+1)=0xb8; + *buffer = 0xc3; + *(buffer+1) = 0xb8; return 2; case 0xcc: // Upper left corner - *buffer=0xe2; - *(buffer+1)=0x8c; - *(buffer+2)=0x9c; + *buffer = 0xe2; + *(buffer+1) = 0x8c; + *(buffer+2) = 0x9c; return 3; case 0xcd: // Upper right corner - *buffer=0xe2; - *(buffer+1)=0x8c; - *(buffer+2)=0x9d; + *buffer = 0xe2; + *(buffer+1) = 0x8c; + *(buffer+2) = 0x9d; return 3; case 0xce: // Lower left corner - *buffer=0xe2; - *(buffer+1)=0x8c; - *(buffer+2)=0x9e; + *buffer = 0xe2; + *(buffer+1) = 0x8c; + *(buffer+2) = 0x9e; return 3; case 0xcf: // Lower right corner - *buffer=0xe2; - *(buffer+1)=0x8c; - *(buffer+2)=0x9f; + *buffer = 0xe2; + *(buffer+1) = 0x8c; + *(buffer+2) = 0x9f; return 3; default: // - *buffer='?'; // I'll do it eventually, I promise + *buffer = '?'; // I'll do it eventually, I promise return 1; // This are weird chars anyway } } -unsigned char cctolower (unsigned char c) +// Encodes a generic string. Note that since we use the encoders for closed +// caption data, text would have to be encoded as CCs... so using special +// characters here it's a bad idea. +static unsigned encode_line(unsigned char *buffer, unsigned char *text) { - if (c>='A' && c<='Z') - return tolower(c); - switch (c) - { - case 0x7d: // uppercase N tilde - return 0x7e; - case 0x90: // capital letter A with acute - return 0x2a; - case 0x91: // capital letter E with acute - return 0x5c; - case 0x92: // capital letter O with acute - return 0x5f; - case 0x93: // capital letter U with acute - return 0x60; - case 0xa2: // uppercase C with cedilla - return 0x7b; - case 0xa0: // uppercase A, grave accent - return 0x88; - case 0xa3: // uppercase E, grave accent - return 0x8a; - case 0xa1: // uppercase A, circumflex - return 0x8b; - case 0xa4: // uppercase E, circumflex - return 0x8c; - case 0xa7: // uppercase I, circumflex - return 0x8d; - case 0xaa: // uppercase O, circumflex - return 0x8e; - case 0xad: // uppercase U, circumflex - return 0x8f; - case 0x94: // capital letter U with diaresis - return 0x95; - case 0xa5: // capital letter E with diaresis - return 0xa6; - case 0xa8: // uppercase I, with diaresis - return 0xa9; - case 0xab: // uppercase U, grave accent - return 0xac; - case 0xb0: // Uppercase A, tilde - return 0xb1; - case 0xb2: // Uppercase I, acute accent - return 0x5e; - case 0xb3: // Uppercase I, grave accent - return 0xb4; - case 0xb5: // Uppercase O, grave accent - return 0xb6; - case 0xb7: // Uppercase O, tilde - return 0xb8; - case 0xc0: // Uppercase A, umlaut - return 0xc1; - case 0xc2: // Uppercase O, umlaut - return 0xc3; - case 0xc8: // Uppercase A, ring - return 0xc9; - case 0xca: // Uppercase O, slash - return 0xcb; - } - return c; -} - -unsigned char cctoupper (unsigned char c) -{ - if (c>='a' && c<='z') - return toupper(c); - switch (c) - { - case 0x7e: // lowercase n tilde - return 0x7d; - case 0x2a: // lowercase a, acute accent - return 0x90; - case 0x5c: // lowercase e, acute accent - return 0x91; - case 0x5e: // lowercase i, acute accent - return 0xb2; - case 0x5f: // lowercase o, acute accent - return 0x92; - case 0x60: // lowercase u, acute accent - return 0x93; - case 0x7b: // lowercase c with cedilla - return 0xa2; - case 0x88: // lowercase a, grave accent - return 0xa0; - case 0x8a: // lowercase e, grave accent - return 0xa3; - case 0x8b: // lowercase a, circumflex accent - return 0xa1; - case 0x8c: // lowercase e, circumflex accent - return 0xa4; - case 0x8d: // lowercase i, circumflex accent - return 0xa7; - case 0x8e: // lowercase o, circumflex accent - return 0xaa; - case 0x8f: // lowercase u, circumflex accent - return 0xad; - case 0x95: // lowercase letter U with diaeresis - return 0x94; - case 0xa6: // lowercase letter e with diaresis - return 0xa5; - case 0xa9: // lowercase i, with diaresis - return 0xa8; - case 0xac: // lowercase u, grave accent - return 0xab; - case 0xb1: // Lowercase a, tilde - return 0xb0; - case 0xb4: // Lowercase i, grave accent - return 0xb3; - case 0xb6: // Lowercase o, grave accent - return 0xb5; - case 0xb8: // Lowercase o, tilde - return 0xb7; - case 0xc1: // Lowercase A, umlaut - return 0xc0; - case 0xc3: // Lowercase o, umlaut - return 0xc2; - case 0xc9: // Lowercase A, ring - return 0xc8; - case 0xcb: // Lowercase o, slash - return 0xca; - } - return c; -} - - -// Encodes a generic string. Note that since we use the encoders for closed caption -// data, text would have to be encoded as CCs... so using special characters here -// it's a bad idea. -unsigned encode_line (unsigned char *buffer, unsigned char *text) -{ - unsigned bytes=0; + unsigned bytes = 0; while (*text) { - switch (encoding) - { - case ENC_UTF_8: - case ENC_LATIN_1: - *buffer=*text; - bytes++; - buffer++; - break; - case ENC_UNICODE: - *buffer=*text; - *(buffer+1)=0; - bytes+=2; - buffer+=2; - break; - } - text++; + *buffer++ = *text++; + bytes++; } return bytes; } -#define ISSEPARATOR(c) (c==' ' || c==0x89 || ispunct(c) \ - || c==0x99) // This is the apostrofe. We get it here in CC encoding, not ASCII - - -void correct_case (int line_num, struct eia608_screen *data) -{ -/* int i=0; */ -/* while (i<spell_words) */ -/* { */ -/* char *c=(char *) data->characters[line_num]; */ -/* size_t len=strlen (spell_correct[i]); */ -/* while ((c=strstr (c,spell_lower[i]))!=NULL) */ -/* { */ -/* // Make sure it's a whole word (start of line or */ -/* // preceded by space, and end of line or followed by */ -/* // space) */ -/* unsigned char prev; */ -/* if (c==(char *) data->characters[line_num]) // Beginning of line... */ -/* prev=' '; // ...Pretend we had a blank before */ -/* else */ -/* prev=*(c-1); */ -/* unsigned char next; */ -/* if (c-(char *) data->characters[line_num]+len==CC608_SCREEN_WIDTH) // End of line... */ -/* next=' '; // ... pretend we have a blank later */ -/* else */ -/* next=*(c+len); */ -/* if ( ISSEPARATOR(prev) && ISSEPARATOR(next)) */ -/* { */ -/* memcpy (c,spell_correct[i],len); */ -/* } */ -/* c++; */ -/* } */ -/* i++; */ -/* } */ -} - -void capitalize (int line_num, struct eia608_screen *data, int *new_sentence) +static void find_limit_characters(unsigned char *line, int *first_non_blank, + int *last_non_blank) { int i; - for (i=0;i<CC608_SCREEN_WIDTH;i++) + *last_non_blank = -1; + *first_non_blank = -1; + for (i = 0; i < CC608_SCREEN_WIDTH; i++) { - switch (data->characters[line_num][i]) + unsigned char c = line[i]; + if (c != ' ' && c != 0x89) { - case ' ': - case 0x89: // This is a transparent space - case '-': - break; - case '.': // Fallthrough - case '?': // Fallthrough - case '!': - case ':': - *new_sentence=1; - break; - default: - if (*new_sentence) - data->characters[line_num][i]=cctoupper (data->characters[line_num][i]); - else - data->characters[line_num][i]=cctolower (data->characters[line_num][i]); - *new_sentence=0; - break; + if (*first_non_blank == -1) + *first_non_blank = i; + *last_non_blank = i; } } } -void find_limit_characters (unsigned char *line, int *first_non_blank, int *last_non_blank) +static unsigned get_decoder_line_encoded(unsigned char *buffer, int line_num, + struct eia608_screen *data) { + uint8_t font_style; + uint8_t prev_font_style = FONT_REGULAR; + uint8_t font_color; + uint8_t prev_font_color = COL_WHITE; int i; - *last_non_blank=-1; - *first_non_blank=-1; - for (i=0;i<CC608_SCREEN_WIDTH;i++) - { - unsigned char c=line[i]; - if (c!=' ' && c!=0x89) - { - if (*first_non_blank==-1) - *first_non_blank=i; - *last_non_blank=i; - } - } -} - -unsigned get_decoder_line_basic (unsigned char *buffer, int line_num, struct eia608_screen *data) -{ unsigned char *line = data->characters[line_num]; - int last_non_blank=-1; - int first_non_blank=-1; - unsigned char *orig=buffer; // Keep for debugging - int i; - find_limit_characters (line, &first_non_blank, &last_non_blank); + unsigned char *orig = buffer; // Keep for debugging + int first = 0, last = 31; - if (first_non_blank==-1) + find_limit_characters(line, &first, &last); + for (i = first; i <= last; i++) { - *buffer=0; - return 0; - } + // Handle color + font_color = data->colors[line_num][i]; + font_style = data->fonts[line_num][i]; - int bytes=0; - for (i=first_non_blank;i<=last_non_blank;i++) - { - char c=line[i]; - switch (encoding) + // Handle reset to defaults + if ((font_style & FONT_STYLE_MASK) == 0 && font_color == COL_WHITE) { - case ENC_UTF_8: - bytes=get_char_in_utf_8 (buffer,c); - break; - case ENC_LATIN_1: - get_char_in_latin_1 (buffer,c); - bytes=1; - break; - case ENC_UNICODE: - get_char_in_unicode (buffer,c); - bytes=2; - break; + if (((font_style ^ prev_font_style) & FONT_STYLE_MASK) || + (font_color != prev_font_color)) + { + buffer += encode_line(buffer, (uint8_t*)"{\\r}"); + } } - buffer+=bytes; - } - *buffer=0; - return (unsigned) (buffer-orig); // Return length -} + else + { + // Open markup + if (((font_style ^ prev_font_style) & FONT_STYLE_MASK) || + (font_color != prev_font_color)) + { + // style changed + buffer += encode_line(buffer, (uint8_t*)"{"); + } -unsigned get_decoder_line_encoded (unsigned char *buffer, int line_num, struct eia608_screen *data) -{ - int col = COL_WHITE; - int underlined = 0; - int italics = 0; - int i; + // Handle underlined + if ((font_style ^ prev_font_style) & FONT_UNDERLINED) + { + int enable = !!(font_style & FONT_UNDERLINED); + buffer += encode_line(buffer, (uint8_t*)"\\u"); + *buffer++ = enable + 0x30; + } - unsigned char *line = data->characters[line_num]; - unsigned char *orig=buffer; // Keep for debugging - int first=0, last=31; - if (trim_subs) - find_limit_characters(line,&first,&last); - for (i=first;i<=last;i++) - { - // Handle color - int its_col = data->colors[line_num][i]; - if (its_col != col && !nofontcolor) - { - if (col!=COL_WHITE) // We need to close the previous font tag + // Handle italics + if ((font_style ^ prev_font_style) & FONT_ITALICS) { - buffer+= encode_line (buffer,(unsigned char *) "</font>"); + int enable = !!(font_style & FONT_ITALICS); + buffer += encode_line(buffer, (uint8_t*)"\\i"); + *buffer++ = enable + 0x30; } - // Add new font tag - buffer+=encode_line (buffer, (unsigned char*) color_text[its_col][1]); - if (its_col==COL_USERDEFINED) + + // Handle color + if (font_color != prev_font_color) { - // The previous sentence doesn't copy the whole - // <font> tag, just up to the quote before the color - buffer+=encode_line (buffer, (unsigned char*) usercolor_rgb); - buffer+=encode_line (buffer, (unsigned char*) "\">"); + buffer += encode_line(buffer, (uint8_t*)"\\1c"); + buffer += encode_line(buffer, + (uint8_t*)color_text[font_color][1]); } - col = its_col; - } - // Handle underlined - int is_underlined = data->fonts[line_num][i] & FONT_UNDERLINED; - if (is_underlined && underlined==0) // Open underline - { - buffer+=encode_line (buffer, (unsigned char *) "<u>"); - } - if (is_underlined==0 && underlined) // Close underline - { - buffer+=encode_line (buffer, (unsigned char *) "</u>"); - } - underlined=is_underlined; - // Handle italics - int has_ita = data->fonts[line_num][i] & FONT_ITALICS; - if (has_ita && italics==0) // Open italics - { - buffer+=encode_line (buffer, (unsigned char *) "<i>"); - } - if (has_ita==0 && italics) // Close italics - { - buffer+=encode_line (buffer, (unsigned char *) "</i>"); - } - italics=has_ita; - int bytes=0; - switch (encoding) - { - case ENC_UTF_8: - bytes=get_char_in_utf_8 (buffer,line[i]); - break; - case ENC_LATIN_1: - get_char_in_latin_1 (buffer,line[i]); - bytes=1; - break; - case ENC_UNICODE: - get_char_in_unicode (buffer,line[i]); - bytes=2; - break; + // Close markup + if (((font_style ^ prev_font_style) & FONT_STYLE_MASK) || + (font_color != prev_font_color)) + { + // style changed + buffer += encode_line(buffer, (uint8_t*)"}"); + } } - buffer+=bytes; - } - if (italics) - { - buffer+=encode_line (buffer, (unsigned char *) "</i>"); - } - if (underlined) - { - buffer+=encode_line (buffer, (unsigned char *) "</u>"); - } - if (col != COL_WHITE && !nofontcolor) - { - buffer+=encode_line (buffer, (unsigned char *) "</font>"); + prev_font_style = font_style; + prev_font_color = font_color; + + int bytes = 0; + bytes = get_char_in_utf8(buffer, line[i]); + buffer += bytes; } - *buffer=0; - return (unsigned) (buffer-orig); // Return length + *buffer = 0; + return (unsigned) (buffer - orig); // Return length } -void delete_all_lines_but_current (struct eia608_screen *data, int row) +static void delete_all_lines_but_current (struct eia608_screen *data, int row) { int i; for (i=0;i<15;i++) @@ -1271,7 +674,7 @@ void delete_all_lines_but_current (struct eia608_screen *data, int row) } } -void clear_eia608_cc_buffer (struct eia608_screen *data) +static void clear_eia608_cc_buffer (struct eia608_screen *data) { int i; @@ -1286,27 +689,26 @@ void clear_eia608_cc_buffer (struct eia608_screen *data) data->empty=1; } -void init_eia608 (struct eia608 *data) +static void init_eia608 (struct eia608 *data) { - data->cursor_column=0; - data->cursor_row=0; + data->cursor_column = 0; + data->cursor_row = 0; clear_eia608_cc_buffer (&data->buffer1); clear_eia608_cc_buffer (&data->buffer2); - data->visible_buffer=1; - data->last_c1=0; - data->last_c2=0; - data->mode=MODE_POPUP; - // data->current_visible_start_cc=0; - data->current_visible_start_ms=0; - data->srt_counter=0; - data->screenfuls_counter=0; - data->channel=1; - data->color=default_color; - data->font=FONT_REGULAR; - data->rollup_base_row=14; + data->visible_buffer = 1; + data->last_c1 = 0; + data->last_c2 = 0; + data->mode = MODE_POPUP; + data->current_visible_start_ms = 0; + data->ssa_counter = 0; + data->screenfuls_counter = 0; + data->channel = 1; + data->color = default_color; + data->font = FONT_REGULAR; + data->rollup_base_row = 14; } -struct eia608_screen *get_current_hidden_buffer(struct s_write *wb) +static struct eia608_screen *get_current_hidden_buffer(struct s_write *wb) { struct eia608_screen *data; if (wb->data608->visible_buffer == 1) @@ -1316,7 +718,7 @@ struct eia608_screen *get_current_hidden_buffer(struct s_write *wb) return data; } -struct eia608_screen *get_current_visible_buffer(struct s_write *wb) +static struct eia608_screen *get_current_visible_buffer(struct s_write *wb) { struct eia608_screen *data; if (wb->data608->visible_buffer == 1) @@ -1331,7 +733,7 @@ static void swap_visible_buffer(struct s_write *wb) wb->data608->visible_buffer = (wb->data608->visible_buffer == 1) ? 2 : 1; } -struct eia608_screen *get_writing_buffer (struct s_write *wb) +static struct eia608_screen *get_writing_buffer(struct s_write *wb) { struct eia608_screen *use_buffer=NULL; switch (wb->data608->mode) @@ -1350,27 +752,28 @@ struct eia608_screen *get_writing_buffer (struct s_write *wb) return use_buffer; } -void write_char (const unsigned char c, struct s_write *wb) +static void write_char(const unsigned char c, struct s_write *wb) { - if (wb->data608->mode!=MODE_TEXT) + if (wb->data608->mode != MODE_TEXT) { - struct eia608_screen * use_buffer=get_writing_buffer(wb); + struct eia608_screen * use_buffer = get_writing_buffer(wb); /* hb_log ("\rWriting char [%c] at %s:%d:%d\n",c, use_buffer == &wb->data608->buffer1?"B1":"B2", wb->data608->cursor_row,wb->data608->cursor_column); */ - use_buffer->characters[wb->data608->cursor_row][wb->data608->cursor_column]=c; - use_buffer->colors[wb->data608->cursor_row][wb->data608->cursor_column]=wb->data608->color; - use_buffer->fonts[wb->data608->cursor_row][wb->data608->cursor_column]=wb->data608->font; - use_buffer->row_used[wb->data608->cursor_row]=1; - use_buffer->empty=0; - if (wb->data608->cursor_column<31) + use_buffer->characters[wb->data608->cursor_row][wb->data608->cursor_column] = c; + use_buffer->colors[wb->data608->cursor_row][wb->data608->cursor_column] = wb->data608->color; + use_buffer->fonts[wb->data608->cursor_row][wb->data608->cursor_column] = wb->data608->font; + use_buffer->row_used[wb->data608->cursor_row] = 1; + use_buffer->empty = 0; + if (wb->data608->cursor_column < 31) wb->data608->cursor_column++; } } /* Handle MID-ROW CODES. */ -void handle_text_attr (const unsigned char c1, const unsigned char c2, struct s_write *wb) +static void handle_text_attr(const unsigned char c1, const unsigned char c2, + struct s_write *wb) { // Handle channel change wb->data608->channel=wb->new_channel; @@ -1389,56 +792,34 @@ void handle_text_attr (const unsigned char c1, const unsigned char c2, struct s_ wb->data608->color=pac2_attribs[i][0]; wb->data608->font=pac2_attribs[i][1]; if (debug_608) - hb_log (" -- Color: %s, font: %s\n", - color_text[wb->data608->color][0], - font_text[wb->data608->font]); + hb_log(" -- Color: %s, font: %s\n", + color_text[wb->data608->color][0], + font_text[wb->data608->font]); if (wb->data608->cursor_column<31) wb->data608->cursor_column++; } } -void mstotime (int64_t milli, unsigned *hours, unsigned *minutes, - unsigned *seconds, unsigned *ms) +static int write_cc_buffer_as_ssa(struct eia608_screen *data, + struct s_write *wb) { - // int64_t milli = (int64_t) ((ccblock*1000)/29.97); - *ms=(unsigned) (milli%1000); // milliseconds - milli=(milli-*ms)/1000; // Remainder, in seconds - *seconds = (int) (milli%60); - milli=(milli-*seconds)/60; // Remainder, in minutes - *minutes = (int) (milli%60); - milli=(milli-*minutes)/60; // Remainder, in hours - *hours=(int) milli; -} - -void fhb_log_encoded (FILE *fh, const char *string) -{ - //GUARANTEE(wb, strlen (string)*3); - //wb->enc_buffer_used=encode_line (wb->enc_buffer,(unsigned char *) string); - //fwrite (wb->enc_buffer,wb->enc_buffer_used,1,fh); -} - -int write_cc_buffer_as_srt(struct eia608_screen *data, struct s_write *wb) -{ - unsigned h1,m1,s1,ms1; - unsigned h2,m2,s2,ms2; int wrote_something = 0; - int64_t ms_start= wb->data608->current_visible_start_ms; int i; + int64_t ms_start = wb->data608->current_visible_start_ms; + //int64_t ms_end = get_last_pts(wb) + subs_delay; + int row = -1, col = -1; - ms_start+=subs_delay; + ms_start += subs_delay; if (ms_start<0) // Drop screens that because of subs_delay start too early return 0; - int64_t ms_end = get_fts(wb)+subs_delay; - mstotime (ms_start,&h1,&m1,&s1,&ms1); - mstotime (ms_end-1,&h2,&m2,&s2,&ms2); // -1 To prevent overlapping with next line. - char timeline[128]; - wb->data608->srt_counter++; - sprintf (timeline,"%u\r\n",wb->data608->srt_counter); - if (debug_608) { - hb_log ("\n- - - SRT caption - - -\n"); + char timeline[128]; + wb->data608->ssa_counter++; + sprintf (timeline,"%u\r\n",wb->data608->ssa_counter); + + hb_log ("\n- - - SSA caption - - -\n"); hb_log ("%s", timeline); } @@ -1447,58 +828,122 @@ int write_cc_buffer_as_srt(struct eia608_screen *data, struct s_write *wb) * ensure that we only have two lines, insert a newline after the first one, * and have a big bottom line (strip spaces from any joined lines). */ + int rows = 0, columns = 0; + for (i = 0; i < 15; i++) + { + if (data->row_used[i]) + { + int first, last; + + rows++; + find_limit_characters(data->characters[i], &first, &last); + if (last - first + 1 > columns) + columns = last - first + 1; + } + } + wb->enc_buffer_used = 0; int line = 1; - for (i=0;i<15;i++) + for (i = 0; i < 15; i++) { if (data->row_used[i]) { - if (sentence_cap) + // Get position for this CC + if (row == -1) { - capitalize(i,data, &wb->new_sentence); - correct_case(i,data); + int last, x, y, safe_zone, cell_width, cell_height; + int cropped_width, cropped_height, font_size; + char *pos; + + row = i; + find_limit_characters(data->characters[i], &col, &last); + + // CC grid is 16 rows by 62 colums + // Our SSA resolution is the title resolution + // Tranlate CC grid to SSA coordinates + // The numbers are tweaked to keep things off the very + // edges of the screen and in the "safe" zone + cropped_height = wb->height - wb->crop[0] - wb->crop[1]; + cropped_width = wb->width - wb->crop[2] - wb->crop[3]; + font_size = cropped_height * .066; + safe_zone = wb->height * 0.025; + cell_height = (wb->height - 2 * safe_zone) / 16; + cell_width = (wb->width - 2 * safe_zone) / 32; + + // Calculate position assuming the position defines + // the baseline of the text which is lower left corner + // of bottom row of characters + y = cell_height * (row + 1 + rows) + safe_zone - wb->crop[0]; + x = cell_width * col + safe_zone - wb->crop[2]; + if (y < 0) + y = (rows * font_size) + safe_zone; + if (x < 0) + x = safe_zone; + if (y > cropped_height) + y = cropped_height - safe_zone; + if (x + columns * cell_width > cropped_width) + x = cropped_width - columns * cell_width - safe_zone; + pos = hb_strdup_printf("{\\a1\\pos(%d,%d)}", x, y); + wb->enc_buffer_used += encode_line( + wb->enc_buffer + wb->enc_buffer_used, (uint8_t*)pos); + free(pos); } + /* - * The intention was to use a newline but QT doesn't like it, old code still - * here just in case.. + * The intention was to use a newline but QT doesn't like it, + * old code still here just in case.. */ if (line == 1) { - wb->enc_buffer_used = get_decoder_line_encoded(wb->enc_buffer, i, data); + wb->enc_buffer_used += get_decoder_line_encoded( + wb->enc_buffer + wb->enc_buffer_used, i, data); line = 2; } else { - wb->enc_buffer_used += encode_line(wb->enc_buffer+wb->enc_buffer_used, - (unsigned char *) "\n"); - wb->enc_buffer_used += get_decoder_line_encoded(wb->enc_buffer+wb->enc_buffer_used, i, data); + wb->enc_buffer_used += encode_line( + wb->enc_buffer + wb->enc_buffer_used, (uint8_t*)"\\N"); + wb->enc_buffer_used += get_decoder_line_encoded( + wb->enc_buffer + wb->enc_buffer_used, i, data); } } } - if (wb->enc_buffer_used) + if (wb->enc_buffer_used && wb->enc_buffer[0] != 0) { - hb_buffer_t *buffer = hb_buffer_init( wb->enc_buffer_used + 1 ); + hb_buffer_t *buffer; + int len; + + // bump past null terminator + wb->enc_buffer_used++; + buffer = hb_buffer_init(wb->enc_buffer_used + SSA_PREAMBLE_LEN); buffer->s.frametype = HB_FRAME_SUBTITLE; buffer->s.start = ms_start; buffer->s.stop = AV_NOPTS_VALUE; - memcpy( buffer->data, wb->enc_buffer, wb->enc_buffer_used + 1 ); - if (wb->hb_last_buffer) { + sprintf((char*)buffer->data, "%d,,Default,,0,0,0,,", ++wb->line); + len = strlen((char*)buffer->data); + memcpy(buffer->data + len, wb->enc_buffer, wb->enc_buffer_used); + if (wb->hb_last_buffer) + { wb->hb_last_buffer->next = buffer; - } else { + } + else + { wb->hb_buffer = buffer; } wb->hb_last_buffer = buffer; - wrote_something=1; wb->clear_sub_needed = 1; } else if (wb->clear_sub_needed) { - hb_buffer_t *buffer = hb_buffer_init( 1 ); + hb_buffer_t *buffer = hb_buffer_init(1); buffer->s.frametype = HB_FRAME_SUBTITLE; buffer->s.start = ms_start; buffer->s.stop = ms_start; buffer->data[0] = 0; - if (wb->hb_last_buffer != NULL) { + if (wb->hb_last_buffer != NULL) + { wb->hb_last_buffer->next = buffer; - } else { + } + else + { wb->hb_buffer = buffer; } wb->hb_last_buffer = buffer; @@ -1511,18 +956,18 @@ int write_cc_buffer_as_srt(struct eia608_screen *data, struct s_write *wb) return wrote_something; } -int write_cc_buffer(struct s_write *wb) +static int write_cc_buffer(struct s_write *wb) { struct eia608_screen *data; int wrote_something=0; data = get_current_visible_buffer(wb); wb->new_sentence=1; - wrote_something = write_cc_buffer_as_srt (data, wb); + wrote_something = write_cc_buffer_as_ssa(data, wb); return wrote_something; } -void roll_up(struct s_write *wb) +static void roll_up(struct s_write *wb) { struct eia608_screen *use_buffer; int i, j; @@ -1532,29 +977,29 @@ void roll_up(struct s_write *wb) switch (wb->data608->mode) { case MODE_ROLLUP_2: - keep_lines=2; + keep_lines = 2; break; case MODE_ROLLUP_3: - keep_lines=3; + keep_lines = 3; break; case MODE_ROLLUP_4: - keep_lines=4; + keep_lines = 4; break; default: // Shouldn't happen - keep_lines=0; + keep_lines = 0; break; } - int firstrow=-1, lastrow=-1; + int firstrow = -1, lastrow = -1; // Look for the last line used - int rows_now=0; // Number of rows in use right now - for (i=0;i<15;i++) + int rows_now = 0; // Number of rows in use right now + for (i = 0; i < 15; i++) { if (use_buffer->row_used[i]) { rows_now++; - if (firstrow==-1) - firstrow=i; - lastrow=i; + if (firstrow == -1) + firstrow = i; + lastrow = i; } } @@ -1564,37 +1009,37 @@ void roll_up(struct s_write *wb) if (lastrow==-1) // Empty screen, nothing to rollup return; - for (j=lastrow-keep_lines+1;j<lastrow; j++) + for (j = lastrow - keep_lines + 1; j < lastrow; j++) { - if (j>=0) + if (j >= 0) { - memcpy (use_buffer->characters[j],use_buffer->characters[j+1],CC608_SCREEN_WIDTH+1); - memcpy (use_buffer->colors[j],use_buffer->colors[j+1],CC608_SCREEN_WIDTH+1); - memcpy (use_buffer->fonts[j],use_buffer->fonts[j+1],CC608_SCREEN_WIDTH+1); - use_buffer->row_used[j]=use_buffer->row_used[j+1]; + memcpy(use_buffer->characters[j], use_buffer->characters[j+1], CC608_SCREEN_WIDTH+1); + memcpy(use_buffer->colors[j], use_buffer->colors[j+1], CC608_SCREEN_WIDTH+1); + memcpy(use_buffer->fonts[j], use_buffer->fonts[j+1], CC608_SCREEN_WIDTH+1); + use_buffer->row_used[j] = use_buffer->row_used[j+1]; } } - for (j=0;j<(1+wb->data608->cursor_row-keep_lines);j++) + for (j = 0; j < (1 + wb->data608->cursor_row - keep_lines); j++) { - memset(use_buffer->characters[j],' ',CC608_SCREEN_WIDTH); - memset(use_buffer->colors[j],COL_WHITE,CC608_SCREEN_WIDTH); - memset(use_buffer->fonts[j],FONT_REGULAR,CC608_SCREEN_WIDTH); - use_buffer->characters[j][CC608_SCREEN_WIDTH]=0; - use_buffer->row_used[j]=0; + memset(use_buffer->characters[j], ' ', CC608_SCREEN_WIDTH); + memset(use_buffer->colors[j], COL_WHITE, CC608_SCREEN_WIDTH); + memset(use_buffer->fonts[j], FONT_REGULAR, CC608_SCREEN_WIDTH); + use_buffer->characters[j][CC608_SCREEN_WIDTH] = 0; + use_buffer->row_used[j] = 0; } - memset(use_buffer->characters[lastrow],' ',CC608_SCREEN_WIDTH); - memset(use_buffer->colors[lastrow],COL_WHITE,CC608_SCREEN_WIDTH); - memset(use_buffer->fonts[lastrow],FONT_REGULAR,CC608_SCREEN_WIDTH); + memset(use_buffer->characters[lastrow], ' ', CC608_SCREEN_WIDTH); + memset(use_buffer->colors[lastrow], COL_WHITE, CC608_SCREEN_WIDTH); + memset(use_buffer->fonts[lastrow], FONT_REGULAR, CC608_SCREEN_WIDTH); - use_buffer->characters[lastrow][CC608_SCREEN_WIDTH]=0; - use_buffer->row_used[lastrow]=0; + use_buffer->characters[lastrow][CC608_SCREEN_WIDTH] = 0; + use_buffer->row_used[lastrow] = 0; // Sanity check - rows_now=0; - for (i=0;i<15;i++) + rows_now = 0; + for (i = 0; i < 15; i++) if (use_buffer->row_used[i]) rows_now++; - if (rows_now>keep_lines) + if (rows_now > keep_lines) hb_log ("Bug in roll_up, should have %d lines but I have %d.\n", keep_lines, rows_now); } @@ -1613,7 +1058,7 @@ void erase_memory (struct s_write *wb, int displayed) clear_eia608_cc_buffer (buf); } -int is_current_row_empty (struct s_write *wb) +static int is_current_row_empty (struct s_write *wb) { struct eia608_screen *use_buffer; int i; @@ -1628,7 +1073,8 @@ int is_current_row_empty (struct s_write *wb) } /* Process GLOBAL CODES */ -void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct s_write *wb) +static void handle_command(unsigned char c1, const unsigned char c2, + struct s_write *wb) { // Handle channel change wb->data608->channel=wb->new_channel; @@ -1674,7 +1120,7 @@ void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct if (wb->data608->cursor_column>0) { wb->data608->cursor_column--; - get_writing_buffer(wb)->characters[wb->data608->cursor_row][wb->data608->cursor_column]=' '; + get_writing_buffer(wb)->characters[wb->data608->cursor_row][wb->data608->cursor_column] = ' '; } break; case COM_TABOFFSET1: @@ -1693,11 +1139,11 @@ void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct break; case COM_RESUMECAPTIONLOADING: wb->data608->mode=MODE_POPUP; - wb->data608->current_visible_start_ms = get_fts(wb); + wb->data608->current_visible_start_ms = get_last_pts(wb); break; case COM_RESUMETEXTDISPLAY: wb->data608->mode=MODE_TEXT; - wb->data608->current_visible_start_ms = get_fts(wb); + wb->data608->current_visible_start_ms = get_last_pts(wb); break; case COM_ROLLUP2: if (wb->data608->mode==MODE_POPUP) @@ -1714,11 +1160,11 @@ void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct handle_command(0x14, 0x2D, wb); wb->rollup_cr = 1; } - wb->data608->current_visible_start_ms = get_fts(wb); + wb->data608->current_visible_start_ms = get_last_pts(wb); wb->data608->mode=MODE_ROLLUP_2; erase_memory (wb, 0); - wb->data608->cursor_column=0; - wb->data608->cursor_row=wb->data608->rollup_base_row; + wb->data608->cursor_column = 0; + wb->data608->cursor_row = wb->data608->rollup_base_row; break; case COM_ROLLUP3: if (wb->data608->mode==MODE_POPUP) @@ -1734,11 +1180,11 @@ void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct handle_command(0x14, 0x2D, wb); wb->rollup_cr = 1; } - wb->data608->current_visible_start_ms = get_fts(wb); + wb->data608->current_visible_start_ms = get_last_pts(wb); wb->data608->mode=MODE_ROLLUP_3; erase_memory (wb, 0); - wb->data608->cursor_column=0; - wb->data608->cursor_row=wb->data608->rollup_base_row; + wb->data608->cursor_column = 0; + wb->data608->cursor_row = wb->data608->rollup_base_row; break; case COM_ROLLUP4: if (wb->data608->mode==MODE_POPUP) @@ -1754,10 +1200,10 @@ void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct handle_command(0x14, 0x2D, wb); wb->rollup_cr = 1; } - wb->data608->current_visible_start_ms = get_fts(wb); - wb->data608->mode=MODE_ROLLUP_4; - wb->data608->cursor_column=0; - wb->data608->cursor_row=wb->data608->rollup_base_row; + wb->data608->current_visible_start_ms = get_last_pts(wb); + wb->data608->mode = MODE_ROLLUP_4; + wb->data608->cursor_column = 0; + wb->data608->cursor_row = wb->data608->rollup_base_row; erase_memory (wb, 0); break; case COM_CARRIAGERETURN: @@ -1768,17 +1214,16 @@ void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct if (wb->rollup_cr && is_current_row_empty(wb)) { wb->rollup_cr = 0; - wb->data608->current_visible_start_ms = get_fts(wb); + wb->data608->current_visible_start_ms = get_last_pts(wb); break; } - if (norollup) delete_all_lines_but_current(get_current_visible_buffer(wb), wb->data608->cursor_row); if (write_cc_buffer(wb)) wb->data608->screenfuls_counter++; roll_up(wb); - wb->data608->cursor_column=0; - wb->data608->current_visible_start_ms = get_fts(wb); + wb->data608->cursor_column = 0; + wb->data608->current_visible_start_ms = get_last_pts(wb); break; case COM_ERASENONDISPLAYEDMEMORY: erase_memory (wb,0); @@ -1791,7 +1236,7 @@ void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct { // If popup, the last pts is the time to remove the // popup from the screen - wb->data608->current_visible_start_ms = get_fts(wb); + wb->data608->current_visible_start_ms = get_last_pts(wb); } // Write "clear" subtitle if necessary write_cc_buffer(wb); @@ -1802,15 +1247,15 @@ void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct if (wb->data608->mode == MODE_POPUP) { swap_visible_buffer(wb); - wb->data608->current_visible_start_ms = get_fts(wb); + wb->data608->current_visible_start_ms = get_last_pts(wb); } if (write_cc_buffer(wb)) wb->data608->screenfuls_counter++; if (wb->data608->mode != MODE_POPUP) swap_visible_buffer(wb); - wb->data608->cursor_column=0; - wb->data608->cursor_row=0; + wb->data608->cursor_column = 0; + wb->data608->cursor_row = 0; wb->data608->color=default_color; wb->data608->font=FONT_REGULAR; break; @@ -1823,14 +1268,15 @@ void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct } } -void handle_end_of_data (struct s_write *wb) +static void handle_end_of_data(struct s_write *wb) { // We issue a EraseDisplayedMemory here so if there's any captions pending // they get written to file. handle_command (0x14, 0x2c, wb); // EDM } -void handle_double (const unsigned char c1, const unsigned char c2, struct s_write *wb) +static void handle_double(const unsigned char c1, const unsigned char c2, + struct s_write *wb) { unsigned char c; if (wb->data608->channel!=cc_channel) @@ -1845,7 +1291,8 @@ void handle_double (const unsigned char c1, const unsigned char c2, struct s_wri } /* Process EXTENDED CHARACTERS */ -unsigned char handle_extended (unsigned char hi, unsigned char lo, struct s_write *wb) +static unsigned char handle_extended(unsigned char hi, unsigned char lo, + struct s_write *wb) { // Handle channel change if (wb->new_channel > 2) @@ -1886,7 +1333,7 @@ unsigned char handle_extended (unsigned char hi, unsigned char lo, struct s_writ } /* Process PREAMBLE ACCESS CODES (PAC) */ -void handle_pac (unsigned char c1, unsigned char c2, struct s_write *wb) +static void handle_pac(unsigned char c1, unsigned char c2, struct s_write *wb) { // Handle channel change if (wb->new_channel > 2) @@ -1926,19 +1373,19 @@ void handle_pac (unsigned char c1, unsigned char c2, struct s_write *wb) int indent=pac2_attribs[c2][2]; if (debug_608) hb_log (" -- Position: %d:%d, color: %s, font: %s\n",row, - indent,color_text[color][0],font_text[font]); - if (wb->data608->mode!=MODE_TEXT) + indent,color_text[color][0],font_text[font]); + if (wb->data608->mode != MODE_TEXT) { // According to Robson, row info is discarded in text mode // but column is accepted - wb->data608->cursor_row=row-1 ; // Since the array is 0 based + wb->data608->cursor_row = row - 1 ; // Since the array is 0 based } - wb->data608->rollup_base_row=row-1; - wb->data608->cursor_column=indent; + wb->data608->rollup_base_row = row - 1; + wb->data608->cursor_column = indent; } -void handle_single (const unsigned char c1, struct s_write *wb) +static void handle_single(const unsigned char c1, struct s_write *wb) { if (c1<0x20 || wb->data608->channel!=cc_channel) return; // We don't allow special stuff here @@ -1950,7 +1397,7 @@ void handle_single (const unsigned char c1, struct s_write *wb) write_char (c1,wb); } -int check_channel (unsigned char c1, struct s_write *wb) +static int check_channel(unsigned char c1, struct s_write *wb) { if (c1==0x14) { @@ -1984,7 +1431,7 @@ int check_channel (unsigned char c1, struct s_write *wb) /* Handle Command, special char or attribute and also check for * channel changes. * Returns 1 if something was written to screen, 0 otherwise */ -int disCommand (unsigned char hi, unsigned char lo, struct s_write *wb) +static int disCommand(unsigned char hi, unsigned char lo, struct s_write *wb) { int wrote_to_screen=0; @@ -2050,7 +1497,8 @@ int disCommand (unsigned char hi, unsigned char lo, struct s_write *wb) return wrote_to_screen; } -void process608 (const unsigned char *data, int length, struct s_write *wb) +static void process608(const unsigned char *data, int length, + struct s_write *wb) { static int textprinted = 0; int i; @@ -2125,7 +1573,7 @@ void process608 (const unsigned char *data, int length, struct s_write *wb) if ( debug_608 && !textprinted && wb->data608->channel==cc_channel ) { // Current FTS information after the characters are shown - //hb_log("Current FTS: %s\n", print_mstime(get_fts())); + //hb_log("Current FTS: %s\n", print_mstime(get_last_pts())); } if (wrote_to_screen && direct_rollup && // If direct_rollup is enabled and @@ -2135,55 +1583,19 @@ void process608 (const unsigned char *data, int length, struct s_write *wb) { // We don't increase screenfuls_counter here. write_cc_buffer(wb); - wb->data608->current_visible_start_ms = get_fts(wb); + wb->data608->current_visible_start_ms = get_last_pts(wb); } } } } - -/* Return a pointer to a string that holds the printable characters - * of the caption data block. FOR DEBUG PURPOSES ONLY! */ -unsigned char *debug_608toASC (unsigned char *cc_data, int channel) -{ - static unsigned char output[3]; - - unsigned char cc_valid = (cc_data[0] & 4) >>2; - unsigned char cc_type = cc_data[0] & 3; - unsigned char hi, lo; - - output[0]=' '; - output[1]=' '; - output[2]='\x00'; - - if (cc_valid && cc_type==channel) - { - hi = cc_data[1] & 0x7F; // Get rid of parity bit - lo = cc_data[2] & 0x7F; // Get rid of parity bit - if (hi>=0x20) - { - output[0]=hi; - output[1]=(lo>=20 ? lo : '.'); - output[2]='\x00'; - } - else - { - output[0]='<'; - output[1]='>'; - output[2]='\x00'; - } - } - return output; -} - - struct hb_work_private_s { hb_job_t * job; struct s_write * cc608; }; -int decccInit( hb_work_object_t * w, hb_job_t * job ) +static int decccInit( hb_work_object_t * w, hb_job_t * job ) { int retval = 1; hb_work_private_t * pv; @@ -2199,6 +1611,9 @@ int decccInit( hb_work_object_t * w, hb_job_t * job ) if( pv->cc608 ) { + pv->cc608->width = job->title->width; + pv->cc608->height = job->title->height; + memcpy(pv->cc608->crop, job->crop, sizeof(int[4])); retval = general_608_init(pv->cc608); if( !retval ) { @@ -2211,10 +1626,17 @@ int decccInit( hb_work_object_t * w, hb_job_t * job ) } } } + if (!retval) + { + // Generate generic SSA Script Info. + int height = job->title->height - job->crop[0] - job->crop[1]; + int width = job->title->width - job->crop[2] - job->crop[3]; + hb_subtitle_add_ssa_header(w->subtitle, width, height); + } return retval; } -int decccWork( hb_work_object_t * w, hb_buffer_t ** buf_in, +static int decccWork( hb_work_object_t * w, hb_buffer_t ** buf_in, hb_buffer_t ** buf_out ) { hb_work_private_t * pv = w->private_data; @@ -2254,7 +1676,7 @@ int decccWork( hb_work_object_t * w, hb_buffer_t ** buf_in, return HB_WORK_OK; } -void decccClose( hb_work_object_t * w ) +static void decccClose( hb_work_object_t * w ) { hb_work_private_t * pv = w->private_data; general_608_close( pv->cc608 ); diff --git a/libhb/deccc608sub.h b/libhb/deccc608sub.h index baf7ca081..3b000f3f2 100644 --- a/libhb/deccc608sub.h +++ b/libhb/deccc608sub.h @@ -1,26 +1,13 @@ /* - * From ccextractor, leave this file as intact and close to the original as possible so that - * it is easy to patch in fixes - even though this file contains code that we don't need. - * - * Note that the SRT sub generation from CC could be useful for mkv subs. + * From ccextractor... */ -#ifndef __deccc608sub_H__ -#define __deccc608sub_H__ +#ifndef __DECCC608SUB_H__ +#define __DECCC608SUB_H__ #include "common.h" struct s_write; -void handle_end_of_data (struct s_write *wb); -void process608 (const unsigned char *data, int length, struct s_write *wb); -void get_char_in_latin_1 (unsigned char *buffer, unsigned char c); -void get_char_in_unicode (unsigned char *buffer, unsigned char c); -int get_char_in_utf_8 (unsigned char *buffer, unsigned char c); -unsigned char cctolower (unsigned char c); -unsigned char cctoupper (unsigned char c); -int general_608_init (struct s_write *wb); -void general_608_close (struct s_write *wb); - #define CC608_SCREEN_WIDTH 32 enum cc_modes @@ -29,7 +16,7 @@ enum cc_modes MODE_ROLLUP_2 = 1, MODE_ROLLUP_3 = 2, MODE_ROLLUP_4 = 3, - MODE_TEXT = 4 + MODE_TEXT = 4 }; enum color_code @@ -41,7 +28,7 @@ enum color_code COL_RED = 4, COL_YELLOW = 5, COL_MAGENTA = 6, - COL_USERDEFINED = 7 + COL_USERDEFINED = 7 }; @@ -53,26 +40,26 @@ enum font_bits FONT_UNDERLINED_ITALICS = 3 }; +#define FONT_STYLE_MASK FONT_UNDERLINED_ITALICS struct eia608_screen // A CC buffer { - unsigned char characters[15][33]; + unsigned char characters[15][33]; unsigned char colors[15][33]; unsigned char fonts[15][33]; // Extra char at the end for a 0 int row_used[15]; // Any data in row? - int empty; // Buffer completely empty? + int empty; // Buffer completely empty? }; struct eia608 { struct eia608_screen buffer1; - struct eia608_screen buffer2; + struct eia608_screen buffer2; int cursor_row, cursor_column; int visible_buffer; - int srt_counter; // Number of subs currently written + int ssa_counter; // Number of subs currently written int screenfuls_counter; // Number of meaningful screenfuls written int64_t current_visible_start_ms; // At what time did the current visible buffer became so? - // unsigned current_visible_start_cc; // At what time did the current visible buffer became so? enum cc_modes mode; unsigned char last_c1, last_c2; int channel; // Currently selected channel @@ -84,7 +71,7 @@ struct eia608 struct s_write { struct eia608 *data608; FILE *fh; - unsigned char *subline; + unsigned char *subline; int new_sentence; int new_channel; int in_xds_mode; @@ -97,7 +84,12 @@ struct s_write { int clear_sub_needed; // Indicates that we need to send a null // subtitle to clear the current subtitle + int rollup_cr; // Flag indicates if CR command performed by rollup + int line; // SSA line number + int width; + int height; + int crop[4]; }; enum command_code @@ -115,7 +107,7 @@ enum command_code COM_CARRIAGERETURN = 10, COM_ERASENONDISPLAYEDMEMORY = 11, COM_BACKSPACE = 12, - COM_RESUMETEXTDISPLAY = 13 + COM_RESUMETEXTDISPLAY = 13 }; enum encoding_type @@ -127,11 +119,11 @@ enum encoding_type enum output_format { - OF_RAW = 0, - OF_SRT = 1, + OF_RAW = 0, + OF_SRT = 1, OF_SAMI = 2, OF_TRANSCRIPT = 3, OF_RCWT = 4 }; -#endif +#endif // __DECCC608SUB_H__ diff --git a/libhb/decsrtsub.c b/libhb/decsrtsub.c index d4e74fdae..5854138a3 100644 --- a/libhb/decsrtsub.c +++ b/libhb/decsrtsub.c @@ -13,6 +13,8 @@ #include <iconv.h> #include <errno.h> #include "hb.h" +#include "colormap.h" +#include "decsrtsub.h" struct start_and_end { unsigned long start, end; @@ -56,17 +58,152 @@ struct hb_work_private_s hb_subtitle_t *subtitle; uint64_t start_time; // In HB time uint64_t stop_time; // In HB time + + int line; // SSA line number }; -static int +static char* srt_markup_to_ssa(char *srt, int *len) +{ + char terminator; + char color[40]; + uint32_t rgb; + + *len = 0; + if (srt[0] != '<' && srt[0] != '{') + return NULL; + + if (srt[0] == '<') + terminator = '>'; + else + terminator = '}'; + + if (srt[1] == 'i' && srt[2] == terminator) + { + *len = 3; + return hb_strdup_printf("{\\i1}"); + } + else if (srt[1] == 'b' && srt[2] == terminator) + { + *len = 3; + return hb_strdup_printf("{\\b1}"); + } + else if (srt[1] == 'u' && srt[2] == terminator) + { + *len = 3; + return hb_strdup_printf("{\\u1}"); + } + else if (srt[1] == '/' && srt[2] == 'i' && srt[3] == terminator) + { + *len = 4; + return hb_strdup_printf("{\\i0}"); + } + else if (srt[1] == '/' && srt[2] == 'b' && srt[3] == terminator) + { + *len = 4; + return hb_strdup_printf("{\\b0}"); + } + else if (srt[1] == '/' && srt[2] == 'u' && srt[3] == terminator) + { + *len = 4; + return hb_strdup_printf("{\\u0}"); + } + else if (srt[0] == '<' && !strncmp(srt + 1, "font", 4)) + { + int match; + match = sscanf(srt + 1, "font color=\"%39[^\"]\">", color); + if (match != 1) + { + return NULL; + } + while (srt[*len] != '>') (*len)++; + (*len)++; + if (color[0] == '#') + rgb = strtol(color + 1, NULL, 16); + else + rgb = hb_rgb_lookup_by_name(color); + return hb_strdup_printf("{\\1c&H%X&}", HB_RGB_TO_BGR(rgb)); + } + else if (srt[0] == '<' && srt[1] == '/' && !strncmp(srt + 2, "font", 4) && + srt[6] == '>') + { + *len = 7; + return hb_strdup_printf("{\\1c&HFFFFFF&}"); + } + + return NULL; +} + +void hb_srt_to_ssa(hb_buffer_t *sub_in, int line) +{ + if (sub_in->size == 0) + return; + + // null terminate input if not already terminated + if (sub_in->data[sub_in->size-1] != 0) + { + hb_buffer_realloc(sub_in, ++sub_in->size); + sub_in->data[sub_in->size - 1] = 0; + } + char * srt = (char*)sub_in->data; + // SSA markup expands a little over SRT, so allocate a bit of extra + // space. More will be realloc'd if needed. + hb_buffer_t * sub = hb_buffer_init(sub_in->size + 80); + char * ssa, *ssa_markup; + int skip, len, pos, ii; + + // Exchange data between input sub and new ssa_sub + // After this, sub_in contains ssa data + hb_buffer_swap_copy(sub_in, sub); + ssa = (char*)sub_in->data; + + sprintf((char*)sub_in->data, "%d,,Default,,0,0,0,,", line); + pos = strlen((char*)sub_in->data); + + ii = 0; + while (srt[ii] != '\0') + { + if ((ssa_markup = srt_markup_to_ssa(srt + ii, &skip)) != NULL) + { + len = strlen(ssa_markup); + hb_buffer_realloc(sub_in, pos + len + 1); + // After realloc, sub_in->data may change + ssa = (char*)sub_in->data; + sprintf(ssa + pos, "%s", ssa_markup); + free(ssa_markup); + pos += len; + ii += skip; + } + else + { + hb_buffer_realloc(sub_in, pos + 4); + // After realloc, sub_in->data may change + ssa = (char*)sub_in->data; + if (srt[ii] == '\n') + { + ssa[pos++] = '\\'; + ssa[pos++] = 'N'; + ii++; + } + else + { + ssa[pos++] = srt[ii++]; + } + } + } + ssa[pos] = '\0'; + sub_in->size = pos + 1; + hb_buffer_close(&sub); +} + +static int read_time_from_string( const char* timeString, struct start_and_end *result ) { // for ex. 00:00:15,248 --> 00:00:16,545 - + long houres1, minutes1, seconds1, milliseconds1, houres2, minutes2, seconds2, milliseconds2; int scanned; - + scanned = sscanf(timeString, "%ld:%ld:%ld,%ld --> %ld:%ld:%ld,%ld\n", &houres1, &minutes1, &seconds1, &milliseconds1, &houres2, &minutes2, &seconds2, &milliseconds2); @@ -207,8 +344,8 @@ static hb_buffer_t *srt_read( hb_work_private_t *pv ) { return NULL; } - - while( reprocess || get_line( pv, line_buffer, sizeof( line_buffer ) ) ) + + while( reprocess || get_line( pv, line_buffer, sizeof( line_buffer ) ) ) { reprocess = 0; switch (pv->current_state) @@ -227,7 +364,7 @@ static hb_buffer_t *srt_read( hb_work_private_t *pv ) } pv->current_entry.duration = timing.end - timing.start; pv->current_entry.offset = timing.start - pv->current_time; - + pv->current_time = timing.end; pv->current_entry.start = timing.start; @@ -276,7 +413,7 @@ static hb_buffer_t *srt_read( hb_work_private_t *pv ) pv->current_state = k_state_potential_new_entry; continue; } - + q = pv->current_entry.text + pv->current_entry.pos; len = strlen( line_buffer ); size = MIN(1024 - pv->current_entry.pos - 1, len ); @@ -329,9 +466,9 @@ static hb_buffer_t *srt_read( hb_work_private_t *pv ) long length; char *p, *q; int line = 1; - uint64_t start_time = ( pv->current_entry.start + + uint64_t start_time = ( pv->current_entry.start + pv->subtitle->config.offset ) * 90; - uint64_t stop_time = ( pv->current_entry.stop + + uint64_t stop_time = ( pv->current_entry.stop + pv->subtitle->config.offset ) * 90; if( !( start_time > pv->start_time && stop_time < pv->stop_time ) ) @@ -396,7 +533,7 @@ static hb_buffer_t *srt_read( hb_work_private_t *pv ) return buffer; } continue; - } + } } } @@ -406,9 +543,9 @@ static hb_buffer_t *srt_read( hb_work_private_t *pv ) long length; char *p, *q; int line = 1; - uint64_t start_time = ( pv->current_entry.start + + uint64_t start_time = ( pv->current_entry.start + pv->subtitle->config.offset ) * 90; - uint64_t stop_time = ( pv->current_entry.stop + + uint64_t stop_time = ( pv->current_entry.stop + pv->subtitle->config.offset ) * 90; if( !( start_time > pv->start_time && stop_time < pv->stop_time ) ) @@ -467,7 +604,7 @@ static hb_buffer_t *srt_read( hb_work_private_t *pv ) { return buffer; } - + return NULL; } @@ -488,7 +625,7 @@ static int decsrtInit( hb_work_object_t * w, hb_job_t * job ) buffer = hb_buffer_init( 0 ); hb_fifo_push( w->fifo_in, buffer); - + pv->current_state = k_state_potential_new_entry; pv->number_of_entries = 0; pv->last_entry_number = 0; @@ -540,14 +677,20 @@ static int decsrtInit( hb_work_object_t * w, hb_job_t * job ) if( !pv->file ) { - hb_error("Could not open the SRT subtitle file '%s'\n", + hb_error("Could not open the SRT subtitle file '%s'\n", w->subtitle->config.src_filename); } else { retval = 0; } } - } - + } + if (!retval) + { + // Generate generic SSA Script Info. + int height = job->title->height - job->crop[0] - job->crop[1]; + int width = job->title->width - job->crop[2] - job->crop[3]; + hb_subtitle_add_ssa_header(w->subtitle, width, height); + } return retval; } @@ -559,9 +702,10 @@ static int decsrtWork( hb_work_object_t * w, hb_buffer_t ** buf_in, hb_buffer_t * out = NULL; out = srt_read( pv ); - if( out ) { + hb_srt_to_ssa(out, ++pv->line); + /* * Keep a buffer in our input fifo so that we get run. */ @@ -573,7 +717,7 @@ static int decsrtWork( hb_work_object_t * w, hb_buffer_t ** buf_in, return HB_WORK_OK; } - return HB_WORK_OK; + return HB_WORK_OK; } static void decsrtClose( hb_work_object_t * w ) diff --git a/libhb/decsrtsub.h b/libhb/decsrtsub.h new file mode 100644 index 000000000..ff7a311e6 --- /dev/null +++ b/libhb/decsrtsub.h @@ -0,0 +1,16 @@ +/* decsrtsub.h + * + * Copyright (c) 2003-2014 HandBrake Team + * This file is part of the HandBrake source code + * Homepage: <http://handbrake.fr/>. + * It may be used under the terms of the GNU General Public License v2. + * For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html + */ + +#ifndef __DECSRTSUB_H__ +#define __DECSRTSUB_H__ + +void hb_srt_to_ssa(hb_buffer_t *sub_in, int line); + +#endif // __DECSRTSUB_H__ + diff --git a/libhb/decssasub.c b/libhb/decssasub.c index de2c3c672..3cac39934 100644 --- a/libhb/decssasub.c +++ b/libhb/decssasub.c @@ -11,42 +11,35 @@ * Converts SSA subtitles to either: * (1) TEXTSUB format: UTF-8 subtitles with limited HTML-style markup (<b>, <i>, <u>), or * (2) PICTURESUB format, using libass. - * + * * SSA format references: * http://www.matroska.org/technical/specs/subtitles/ssa.html * http://moodub.free.fr/video/ass-specs.doc * vlc-1.0.4/modules/codec/subtitles/subsass.c:ParseSSAString - * + * * libass references: * libass-0.9.9/ass.h * vlc-1.0.4/modules/codec/libass.c - * + * * @author David Foster (davidfstr) */ #include <stdlib.h> #include <stdio.h> +#include <ctype.h> #include "hb.h" #include <ass/ass.h> +#include "decssasub.h" +#include "colormap.h" struct hb_work_private_s { // If decoding to PICTURESUB format: int readOrder; - int raw; hb_job_t *job; }; -typedef enum { - BOLD = 0x01, - ITALIC = 0x02, - UNDERLINE = 0x04 -} StyleSet; - -// "<b></b>".len + "<i></i>".len + "<u></u>".len -#define MAX_OVERHEAD_PER_OVERRIDE (7 * 3) - #define SSA_2_HB_TIME(hr,min,sec,centi) \ ( 90L * ( hr * 1000L * 60 * 60 +\ min * 1000L * 60 +\ @@ -55,71 +48,189 @@ typedef enum { #define SSA_VERBOSE_PACKETS 0 -static StyleSet ssa_parse_style_override( uint8_t *pos, StyleSet prevStyles ) +static int ssa_update_style(char *ssa, hb_subtitle_style_t *style) { - StyleSet nextStyles = prevStyles; - for (;;) + int pos, end, index; + + if (ssa[0] != '{') + return 0; + + pos = 1; + while (ssa[pos] != '}' && ssa[pos] != '\0') { - // Skip over leading '{' or last '\\' + index = -1; + + // Skip any malformed markup junk + while (strchr("\\}", ssa[pos]) == NULL) pos++; pos++; - - // Scan for next \code - while ( *pos != '\\' && *pos != '}' && *pos != '\0' ) pos++; - if ( *pos != '\\' ) + // Check for an index that is in some markup (e.g. font color) + if (isdigit(ssa[pos])) + { + index = ssa[pos++] - 0x30; + } + // Find the end of this markup clause + end = pos; + while (strchr("\\}", ssa[end]) == NULL) end++; + // Handle simple integer valued attributes + if (strchr("ibu", ssa[pos]) != NULL && isdigit(ssa[pos+1])) { - // End of style override block - break; + int val = strtol(ssa + pos + 1, NULL, 0); + switch (ssa[pos]) + { + case 'i': + style->flags = (style->flags & ~HB_STYLE_FLAG_ITALIC) | + !!val * HB_STYLE_FLAG_ITALIC; + break; + case 'b': + style->flags = (style->flags & ~HB_STYLE_FLAG_BOLD) | + !!val * HB_STYLE_FLAG_BOLD; + break; + case 'u': + style->flags = (style->flags & ~HB_STYLE_FLAG_UNDERLINE) | + !!val * HB_STYLE_FLAG_UNDERLINE; + break; + } } - - // If next chars are \[biu][01], interpret it - if ( strchr("biu", pos[1]) && strchr("01", pos[2]) ) + if (ssa[pos] == 'c' && ssa[pos+1] == '&' && ssa[pos+2] == 'H') { - StyleSet styleID = - pos[1] == 'b' ? BOLD : - pos[1] == 'i' ? ITALIC : - pos[1] == 'u' ? UNDERLINE : 0; - int enabled = (pos[2] == '1'); - - if (enabled) + // Font color markup + char *endptr; + uint32_t bgr; + + bgr = strtol(ssa + pos + 3, &endptr, 16); + if (*endptr == '&') { - nextStyles |= styleID; + switch (index) + { + case -1: + case 1: + style->fg_rgb = HB_BGR_TO_RGB(bgr); + break; + case 2: + style->alt_rgb = HB_BGR_TO_RGB(bgr); + break; + case 3: + style->ol_rgb = HB_BGR_TO_RGB(bgr); + break; + case 4: + style->bg_rgb = HB_BGR_TO_RGB(bgr); + break; + default: + // Unknown color index, ignore + break; + } } - else + } + if ((ssa[pos] == 'a' && ssa[pos+1] == '&' && ssa[pos+2] == 'H') || + (!strcmp(ssa+pos, "alpha") && ssa[pos+5] == '&' && ssa[pos+6] == 'H')) + { + // Font alpha markup + char *endptr; + uint8_t alpha; + int alpha_pos = 3; + + if (ssa[1] == 'l') + alpha_pos = 7; + + alpha = strtol(ssa + pos + alpha_pos, &endptr, 16); + if (*endptr == '&') { - nextStyles &= ~styleID; + // SSA alpha is inverted 0 is opaque + alpha = 255 - alpha; + switch (index) + { + case -1: + case 1: + style->fg_alpha = alpha; + break; + case 2: + style->alt_alpha = alpha; + break; + case 3: + style->ol_alpha = alpha; + break; + case 4: + style->bg_alpha = alpha; + break; + default: + // Unknown alpha index, ignore + break; + } } } + pos = end; } - return nextStyles; + if (ssa[pos] == '}') + pos++; + return pos; } -static void ssa_append_html_tags_for_style_change( - uint8_t **dst, StyleSet prevStyles, StyleSet nextStyles ) +char * hb_ssa_to_text(char *in, int *consumed, hb_subtitle_style_t *style) { - #define APPEND(str) { \ - char *src = str; \ - while (*src) { *(*dst)++ = *src++; } \ + int markup_len = 0; + int in_pos = 0; + int out_pos = 0; + char *out = malloc(strlen(in) + 1); // out will never be longer than in + + for (in_pos = 0; in[in_pos] != '\0'; in_pos++) + { + if ((markup_len = ssa_update_style(in + in_pos, style))) + { + *consumed = in_pos + markup_len; + out[out_pos++] = '\0'; + return out; + } + // Check escape codes + if (in[in_pos] == '\\') + { + in_pos++; + switch (in[in_pos]) + { + case '\0': + in_pos--; + break; + case 'N': + case 'n': + out[out_pos++] = '\n'; + break; + case 'h': + out[out_pos++] = ' '; + break; + default: + out[out_pos++] = in[in_pos]; + break; + } + } + else + { + out[out_pos++] = in[in_pos]; + } } + *consumed = in_pos; + out[out_pos++] = '\0'; + return out; +} + +void hb_ssa_style_init(hb_subtitle_style_t *style) +{ + style->flags = 0; + + style->fg_rgb = 0x00FFFFFF; + style->alt_rgb = 0x00FFFFFF; + style->ol_rgb = 0x000F0F0F; + style->bg_rgb = 0x000F0F0F; - // Reverse-order close all previous styles - if (prevStyles & UNDERLINE) APPEND("</u>"); - if (prevStyles & ITALIC) APPEND("</i>"); - if (prevStyles & BOLD) APPEND("</b>"); - - // Forward-order open all next styles - if (nextStyles & BOLD) APPEND("<b>"); - if (nextStyles & ITALIC) APPEND("<i>"); - if (nextStyles & UNDERLINE) APPEND("<u>"); - - #undef APPEND + style->fg_alpha = 0xFF; + style->alt_alpha = 0xFF; + style->ol_alpha = 0xFF; + style->bg_alpha = 0xFF; } -static hb_buffer_t *ssa_decode_line_to_utf8( uint8_t *in_data, int in_size, int in_sequence ); static hb_buffer_t *ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, uint8_t *in_data, int in_size, int in_sequence ); /* * Decodes a single SSA packet to one or more TEXTSUB or PICTURESUB subtitle packets. - * + * * SSA packet format: * ( Dialogue: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text CR LF ) + * 1 2 3 4 5 6 7 8 9 10 @@ -129,7 +240,7 @@ static hb_buffer_t *ssa_decode_packet( hb_work_object_t * w, hb_buffer_t *in ) // Store NULL after the end of the buffer to make using string processing safe hb_buffer_realloc(in, ++in->size); in->data[in->size - 1] = '\0'; - + hb_buffer_t *out_list = NULL; hb_buffer_t **nextPtr = &out_list; @@ -142,53 +253,31 @@ static hb_buffer_t *ssa_decode_packet( hb_work_object_t * w, hb_buffer_t *in ) // Skip empty lines and spaces between adjacent CR and LF if (curLine[0] == '\0') continue; - + // Decode an individual SSA line hb_buffer_t *out; - if ( w->subtitle->config.dest == PASSTHRUSUB ) { - out = ssa_decode_line_to_utf8( (uint8_t *) curLine, strlen( curLine ), in->sequence ); - if ( out == NULL ) - continue; - - // We shouldn't be storing the extra NULL character, - // but the MP4 muxer expects this, unfortunately. - if (out->size > 0 && out->data[out->size - 1] != '\0') - { - hb_buffer_realloc(out, ++out->size); - out->data[out->size - 1] = '\0'; - } - - // If the input packet was non-empty, do not pass through - // an empty output packet (even if the subtitle was empty), - // as this would be interpreted as an end-of-stream - if ( in->size > 0 && out->size == 0 ) { - hb_buffer_close(&out); - continue; - } - } else if ( w->subtitle->config.dest == RENDERSUB ) { - out = ssa_decode_line_to_mkv_ssa( w, (uint8_t *) curLine, strlen( curLine ), in->sequence ); - if ( out == NULL ) - continue; - } - + out = ssa_decode_line_to_mkv_ssa(w, (uint8_t *)curLine, strlen(curLine), in->sequence); + if ( out == NULL ) + continue; + // Append 'out' to 'out_list' *nextPtr = out; nextPtr = &out->next; } - // For point-to-point encoding, when the start time of the stream + // For point-to-point encoding, when the start time of the stream // may be offset, the timestamps of the subtitles must be offset as well. // // HACK: Here we are making the assumption that, under normal circumstances, // the output display time of the first output packet is equal to the // display time of the input packet. - // - // During point-to-point encoding, the display time of the input + // + // During point-to-point encoding, the display time of the input // packet will be offset to compensate. - // - // Therefore we offset all of the output packets by a slip amount - // such that first output packet's display time aligns with the - // input packet's display time. This should give the correct time + // + // Therefore we offset all of the output packets by a slip amount + // such that first output packet's display time aligns with the + // input packet's display time. This should give the correct time // when point-to-point encoding is in effect. if (out_list && out_list->s.start > in->s.start) { @@ -203,13 +292,13 @@ static hb_buffer_t *ssa_decode_packet( hb_work_object_t * w, hb_buffer_t *in ) out = out->next; } } - + return out_list; } /* * Parses the start and stop time from the specified SSA packet. - * + * * Returns true if parsing failed; false otherwise. */ static int parse_timing_from_ssa_packet( char *in_data, int64_t *in_start, int64_t *in_stop ) @@ -223,7 +312,7 @@ static int parse_timing_from_ssa_packet( char *in_data, int64_t *in_start, int64 // 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 + // expected ' ' after the ':' will be the character it matches on // when there is no layer field. int numPartsRead = sscanf( (char *) in_data, "Dialogue:%*128[^,]," "%d:%d:%d.%d," // Start @@ -232,10 +321,10 @@ static int parse_timing_from_ssa_packet( char *in_data, int64_t *in_start, int64 &end_hr, &end_min, &end_sec, &end_centi ); if ( numPartsRead != 8 ) return 1; - + *in_start = SSA_2_HB_TIME(start_hr, start_min, start_sec, start_centi); *in_stop = SSA_2_HB_TIME( end_hr, end_min, end_sec, end_centi); - + return 0; } @@ -258,137 +347,7 @@ static uint8_t *find_field( uint8_t *pos, uint8_t *end, int fieldNum ) * 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 - */ -static hb_buffer_t *ssa_decode_line_to_utf8( uint8_t *in_data, int in_size, int in_sequence ) -{ - uint8_t *pos = in_data; - uint8_t *end = in_data + in_size; - - // Parse values for in->s.start and in->s.stop - int64_t in_start, in_stop; - if ( parse_timing_from_ssa_packet( (char *) in_data, &in_start, &in_stop ) ) - goto fail; - - uint8_t *textFieldPos = find_field( pos, end, 10 ); - if ( textFieldPos == NULL ) - goto fail; - - // Count the number of style overrides in the Text field - int numStyleOverrides = 0; - pos = textFieldPos; - while ( pos < end ) - { - if (*pos++ == '{') - { - numStyleOverrides++; - } - } - - int maxOutputSize = (end - textFieldPos) + ((numStyleOverrides + 1) * MAX_OVERHEAD_PER_OVERRIDE); - hb_buffer_t *out = hb_buffer_init( maxOutputSize ); - if ( out == NULL ) - return NULL; - - /* - * The Text field contains plain text marked up with: - * (1) '\n' -> space - * (2) '\N' -> newline - * (3) curly-brace control codes like '{\k44}' -> HTML tags / strip - * - * Perform the above conversions and copy it to the output packet - */ - StyleSet prevStyles = 0; - uint8_t *dst = out->data; - pos = textFieldPos; - while ( pos < end ) - { - if ( pos[0] == '\\' && pos[1] == 'n' ) - { - *dst++ = ' '; - pos += 2; - } - else if ( pos[0] == '\\' && pos[1] == 'N' ) - { - *dst++ = '\n'; - pos += 2; - } - else if ( pos[0] == '{' ) - { - // Parse SSA style overrides and append appropriate HTML style tags - StyleSet nextStyles = ssa_parse_style_override( pos, prevStyles ); - ssa_append_html_tags_for_style_change( &dst, prevStyles, nextStyles ); - prevStyles = nextStyles; - - // Skip past SSA control code - while ( pos < end && *pos != '}' ) pos++; - if ( pos < end && *pos == '}' ) pos++; - } - else - { - // Copy raw character - *dst++ = *pos++; - } - } - - // Append closing HTML style tags - ssa_append_html_tags_for_style_change( &dst, prevStyles, 0 ); - - // Trim output buffer to the actual amount of data written - out->size = dst - out->data; - - // Copy metadata from the input packet to the output packet - out->s.frametype = HB_FRAME_SUBTITLE; - out->s.start = in_start; - out->s.stop = in_stop; - out->sequence = in_sequence; - - return out; - -fail: - hb_log( "decssasub: malformed SSA subtitle packet: %.*s\n", in_size, in_data ); - return NULL; -} - -static hb_buffer_t * ssa_to_mkv_ssa( hb_work_object_t * w, hb_buffer_t * in ) -{ - hb_buffer_t * out_last = NULL; - hb_buffer_t * out_first = NULL; - - // Store NULL after the end of the buffer to make using string processing safe - hb_buffer_realloc(in, ++in->size); - in->data[in->size - 1] = '\0'; - - const char *EOL = "\r\n"; - char *curLine, *curLine_parserData; - for ( curLine = strtok_r( (char *) in->data, EOL, &curLine_parserData ); - curLine; - curLine = strtok_r( NULL, EOL, &curLine_parserData ) ) - { - hb_buffer_t * out; - - out = ssa_decode_line_to_mkv_ssa( w, (uint8_t *) curLine, strlen( curLine ), in->sequence ); - if( out ) - { - if ( out_last == NULL ) - { - out_last = out_first = out; - } - else - { - out_last->next = out; - out_last = out; - } - } - } - - return out_first; -} - -/* - * 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 @@ -397,22 +356,11 @@ static hb_buffer_t *ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, uint8_t *i { hb_work_private_t * pv = w->private_data; hb_buffer_t * out; - + // Parse values for in->s.start and in->s.stop int64_t in_start, in_stop; if ( parse_timing_from_ssa_packet( (char *) in_data, &in_start, &in_stop ) ) goto fail; - - if (pv->raw) - { - out = hb_buffer_init(in_size + 3); - snprintf((char*)out->data, in_size + 3, "%s\r\n", in_data); - out->s.frametype = HB_FRAME_SUBTITLE; - out->s.start = in_start; - out->s.stop = in_stop; - out->sequence = in_sequence; - return out; - } // Convert the SSA packet to MKV-SSA format, which is what libass expects char *mkvIn; @@ -424,18 +372,18 @@ static hb_buffer_t *ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, uint8_t *i // 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 + // expected ' ' after the ':' will be the character it matches on // when there is no layer field. numPartsRead = sscanf( (char *)in_data, "Dialogue:%128[^,],", layerField ); if ( numPartsRead != 1 ) goto fail; - + styleToTextFields = (char *)find_field( in_data, in_data + in_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; @@ -449,9 +397,9 @@ static hb_buffer_t *ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, uint8_t *i strcat( mkvIn, "," ); strcat( mkvIn, stripLayerField ); strcat( mkvIn, "," ); - strcat( mkvIn, (char *) styleToTextFields ); - - out->size = strlen(mkvIn); + strcat( mkvIn, (char *)styleToTextFields ); + + out->size = strlen(mkvIn) + 1; out->s.frametype = HB_FRAME_SUBTITLE; out->s.start = in_start; out->s.stop = in_stop; @@ -461,11 +409,11 @@ static hb_buffer_t *ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, uint8_t *i { hb_buffer_close(&out); } - + free( layerField ); - + return out; - + fail: hb_log( "decssasub: malformed SSA subtitle packet: %.*s\n", in_size, in_data ); return NULL; @@ -479,24 +427,18 @@ static int decssaInit( hb_work_object_t * w, hb_job_t * job ) w->private_data = pv; pv->job = job; - if (job->mux & HB_MUX_MASK_AV && w->subtitle->config.dest != RENDERSUB ) - { - pv->raw = 1; - } - return 0; } static int decssaWork( hb_work_object_t * w, hb_buffer_t ** buf_in, hb_buffer_t ** buf_out ) { - hb_work_private_t * pv = w->private_data; 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 - + if ( in->size <= 0 ) { *buf_out = in; @@ -504,15 +446,7 @@ static int decssaWork( hb_work_object_t * w, hb_buffer_t ** buf_in, return HB_WORK_DONE; } - if (w->subtitle->config.dest == PASSTHRUSUB && - (pv->job->mux & HB_MUX_MASK_MKV)) - { - *buf_out = ssa_to_mkv_ssa(w, in); - } - else - { - *buf_out = ssa_decode_packet(w, in); - } + *buf_out = ssa_decode_packet(w, in); return HB_WORK_OK; } diff --git a/libhb/decssasub.h b/libhb/decssasub.h new file mode 100644 index 000000000..65bc39187 --- /dev/null +++ b/libhb/decssasub.h @@ -0,0 +1,35 @@ +/* decssasub.h + * + * Copyright (c) 2003-2014 HandBrake Team + * This file is part of the HandBrake source code + * Homepage: <http://handbrake.fr/>. + * It may be used under the terms of the GNU General Public License v2. + * For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html + */ + +#ifndef __DECSSASUB_H__ +#define __DECSSASUB_H__ + +typedef struct +{ + uint32_t flags; + + uint32_t fg_rgb; // forground color + uint32_t alt_rgb; // secondary color + uint32_t ol_rgb; // outline color + uint32_t bg_rgb; // background color + + uint32_t fg_alpha; // forground alpha + uint32_t alt_alpha; // secondary alpha + uint32_t ol_alpha; // outline alpha + uint32_t bg_alpha; // background alpha +} hb_subtitle_style_t; + +#define HB_STYLE_FLAG_ITALIC 0x0001 +#define HB_STYLE_FLAG_BOLD 0x0002 +#define HB_STYLE_FLAG_UNDERLINE 0x0004 + +char * hb_ssa_to_text(char *in, int *consumed, hb_subtitle_style_t *style); +void hb_ssa_style_init(hb_subtitle_style_t *style); + +#endif // __DECSSASUB_H__ diff --git a/libhb/dectx3gsub.c b/libhb/dectx3gsub.c index 4ca165ca8..7a38b1d84 100644 --- a/libhb/dectx3gsub.c +++ b/libhb/dectx3gsub.c @@ -9,17 +9,23 @@ /* * Converts TX3G subtitles to UTF-8 subtitles with limited HTML-style markup (<b>, <i>, <u>). - * + * * TX3G == MPEG 4, Part 17 (ISO/IEC 14496-17) == 3GPP Timed Text (26.245) * A full reference to the format can be found here: * http://www.3gpp.org/ftp/Specs/html-info/26245.htm - * + * * @author David Foster (davidfstr) */ #include <stdlib.h> #include <stdio.h> #include "hb.h" +#include "colormap.h" + +struct hb_work_private_s +{ + int line; // SSA line number +}; typedef enum { BOLD = 0x1, @@ -27,9 +33,8 @@ typedef enum { UNDERLINE = 0x4 } FaceStyleFlag; -#define NUM_FACE_STYLE_FLAGS 3 -#define MAX_OPEN_TAG_SIZE 3 // "<b>" -#define MAX_CLOSE_TAG_SIZE 4 // "</b>" +#define MAX_MARKUP_LEN 40 +#define SSA_PREAMBLE_LEN 24 typedef struct { uint16_t startChar; // NOTE: indices in terms of *character* (not: byte) positions @@ -48,8 +53,6 @@ typedef struct { #define SKIP_ARRAY(n) pos += n; #define WRITE_CHAR(c) {dst[0]=c; dst += 1;} -#define WRITE_START_TAG(c) {dst[0]='<'; dst[1]=c; dst[2]='>'; dst += 3;} -#define WRITE_END_TAG(c) {dst[0]='<'; dst[1]='/'; dst[2]=c; dst[3]='>'; dst += 4;} #define FOURCC(str) ((((uint32_t) str[0]) << 24) | \ (((uint32_t) str[1]) << 16) | \ @@ -57,125 +60,160 @@ typedef struct { (((uint32_t) str[3]) << 0)) #define IS_10xxxxxx(c) ((c & 0xC0) == 0x80) -static hb_buffer_t *tx3g_decode_to_utf8( hb_buffer_t *in ) +static int write_ssa_markup(char *dst, StyleRecord *style) +{ + if (style == NULL) + { + sprintf(dst, "{\\r}"); + return strlen(dst); + } + sprintf(dst, "{\\i%d\\b%d\\u%d\\1c&H%X&\\1a&H%02X&}", + !!(style->faceStyleFlags & ITALIC), + !!(style->faceStyleFlags & BOLD), + !!(style->faceStyleFlags & UNDERLINE), + HB_RGB_TO_BGR(style->textColorRGBA >> 8), + 255 - (style->textColorRGBA & 0xFF)); // SSA alpha is inverted 0==opaque + + return strlen(dst); +} + +static hb_buffer_t *tx3g_decode_to_ssa(hb_buffer_t *in, int line) { uint8_t *pos = in->data; uint8_t *end = in->data + in->size; - + uint16_t numStyleRecords = 0; - - uint8_t *startStyle; - uint8_t *endStyle; - + StyleRecord *styleRecords = NULL; + /* * Parse the packet as a TX3G TextSample. - * + * * Look for a single StyleBox ('styl') and read all contained StyleRecords. * Ignore all other box types. - * + * * NOTE: Buffer overflows on read are not checked. */ uint16_t textLength = READ_U16(); uint8_t *text = READ_ARRAY(textLength); - startStyle = calloc( textLength, 1 ); - endStyle = calloc( textLength, 1 ); - while ( pos < end ) { + while ( pos < end ) + { /* * Read TextSampleModifierBox */ uint32_t size = READ_U32(); - if ( size == 0 ) { + if ( size == 0 ) + { size = pos - end; // extends to end of packet } - if ( size == 1 ) { + if ( size == 1 ) + { hb_log( "dectx3gsub: TextSampleModifierBox has unsupported large size" ); break; } uint32_t type = READ_U32(); - if ( type == FOURCC("uuid") ) { + if (type == FOURCC("uuid")) + { hb_log( "dectx3gsub: TextSampleModifierBox has unsupported extended type" ); break; } - - if ( type == FOURCC("styl") ) { + + if (type == FOURCC("styl")) + { // Found a StyleBox. Parse the contained StyleRecords - - if ( numStyleRecords != 0 ) { + + if ( numStyleRecords != 0 ) + { hb_log( "dectx3gsub: found additional StyleBoxes on subtitle; skipping" ); SKIP_ARRAY(size); continue; } - + numStyleRecords = READ_U16(); - + if (numStyleRecords > 0) + styleRecords = calloc(numStyleRecords, sizeof(StyleRecord)); + int i; - for (i=0; i<numStyleRecords; i++) { - StyleRecord curRecord; - curRecord.startChar = READ_U16(); - curRecord.endChar = READ_U16(); - curRecord.fontID = READ_U16(); - curRecord.faceStyleFlags = READ_U8(); - curRecord.fontSize = READ_U8(); - curRecord.textColorRGBA = READ_U32(); - - startStyle[curRecord.startChar] |= curRecord.faceStyleFlags; - endStyle[curRecord.endChar] |= curRecord.faceStyleFlags; + for (i = 0; i < numStyleRecords; i++) + { + styleRecords[i].startChar = READ_U16(); + styleRecords[i].endChar = READ_U16(); + styleRecords[i].fontID = READ_U16(); + styleRecords[i].faceStyleFlags = READ_U8(); + styleRecords[i].fontSize = READ_U8(); + styleRecords[i].textColorRGBA = READ_U32(); } - } else { + } + else + { // Found some other kind of TextSampleModifierBox. Skip it. SKIP_ARRAY(size); } } - + /* * Copy text to output buffer, and add HTML markup for the style records */ - int maxOutputSize = textLength + (numStyleRecords * NUM_FACE_STYLE_FLAGS * (MAX_OPEN_TAG_SIZE + MAX_CLOSE_TAG_SIZE)); + int maxOutputSize = textLength + SSA_PREAMBLE_LEN + (numStyleRecords * MAX_MARKUP_LEN); hb_buffer_t *out = hb_buffer_init( maxOutputSize ); if ( out == NULL ) goto fail; uint8_t *dst = out->data; int charIndex = 0; - for ( pos = text, end = text + textLength; pos < end; pos++ ) { - if (IS_10xxxxxx(*pos)) { + int styleIndex = 0; + + sprintf((char*)dst, "%d,,Default,,0,0,0,,", line); + dst += strlen((char*)dst); + for (pos = text, end = text + textLength; pos < end; pos++) + { + if (IS_10xxxxxx(*pos)) + { // Is a non-first byte of a multi-byte UTF-8 character WRITE_CHAR(*pos); continue; // ...without incrementing 'charIndex' } - - uint8_t plusStyles = startStyle[charIndex]; - uint8_t minusStyles = endStyle[charIndex]; - - if (minusStyles & UNDERLINE) - WRITE_END_TAG('u'); - if (minusStyles & ITALIC) - WRITE_END_TAG('i'); - if (minusStyles & BOLD) - WRITE_END_TAG('b'); - - if (plusStyles & BOLD) - WRITE_START_TAG('b'); - if (plusStyles & ITALIC) - WRITE_START_TAG('i'); - if (plusStyles & UNDERLINE) - WRITE_START_TAG('u'); - - WRITE_CHAR(*pos); + + if (styleIndex < numStyleRecords) + { + if (styleRecords[styleIndex].endChar == charIndex) + { + if (styleIndex + 1 >= numStyleRecords || + styleRecords[styleIndex+1].startChar > charIndex) + { + dst += write_ssa_markup((char*)dst, NULL); + } + styleIndex++; + } + if (styleRecords[styleIndex].startChar == charIndex) + { + dst += write_ssa_markup((char*)dst, &styleRecords[styleIndex]); + } + } + + if (*pos == '\n') + { + WRITE_CHAR('\\'); + WRITE_CHAR('N'); + } + else + { + WRITE_CHAR(*pos); + } charIndex++; } - + *dst = '\0'; + dst++; + // Trim output buffer to the actual amount of data written out->size = dst - out->data; - + // Copy metadata from the input packet to the output packet out->s.frametype = HB_FRAME_SUBTITLE; out->s.start = in->s.start; out->s.stop = in->s.stop; - + fail: - free( startStyle ); - free( endStyle ); - + free(styleRecords); + return out; } @@ -191,55 +229,48 @@ fail: static int dectx3gInit( hb_work_object_t * w, hb_job_t * job ) { + hb_work_private_t * pv; + pv = calloc( 1, sizeof( hb_work_private_t ) ); + if (pv == NULL) + return 1; + w->private_data = pv; + + // TODO: + // parse w->subtitle->extradata txg3 sample description into + // SSA format and replace extradata. + // For now we just create a generic SSA Script Info. + int height = job->title->height - job->crop[0] - job->crop[1]; + int width = job->title->width - job->crop[2] - job->crop[3]; + hb_subtitle_add_ssa_header(w->subtitle, width, height); + return 0; } static int dectx3gWork( hb_work_object_t * w, hb_buffer_t ** buf_in, hb_buffer_t ** buf_out ) { + hb_work_private_t * pv = w->private_data; hb_buffer_t * in = *buf_in; - hb_buffer_t * out = NULL; - - // Warn if the subtitle's duration has not been passed through by the demuxer, - // which will prevent the subtitle from displaying at all + if ( in->s.stop == 0 ) { hb_log( "dectx3gsub: subtitle packet lacks duration" ); } - - if ( in->size > 0 ) { - out = tx3g_decode_to_utf8(in); - } else { - out = hb_buffer_init( 0 ); - } - - if ( out != NULL ) { - // We shouldn't be storing the extra NULL character, - // but the MP4 muxer expects this, unfortunately. - if (out->size > 0 && out->data[out->size - 1] != '\0') - { - hb_buffer_realloc(out, ++out->size); - out->data[out->size - 1] = '\0'; - } - - // If the input packet was non-empty, do not pass through - // an empty output packet (even if the subtitle was empty), - // as this would be interpreted as an end-of-stream - if ( in->size > 0 && out->size == 0 ) { - hb_buffer_close(&out); - } + + if (in->size == 0) + { + *buf_out = in; + *buf_in = NULL; + return HB_WORK_DONE; } - - // Dispose the input packet, as it is no longer needed - hb_buffer_close(&in); - - *buf_in = NULL; - *buf_out = out; + + *buf_out = tx3g_decode_to_ssa(in, ++pv->line); + return HB_WORK_OK; } static void dectx3gClose( hb_work_object_t * w ) { - // nothing + free(w->private_data); } hb_work_object_t hb_dectx3gsub = diff --git a/libhb/decutf8sub.c b/libhb/decutf8sub.c index fac5b31a8..b8ea9bf5f 100644 --- a/libhb/decutf8sub.c +++ b/libhb/decutf8sub.c @@ -19,15 +19,33 @@ #include <stdlib.h> #include <stdio.h> #include "hb.h" +#include "decsrtsub.h" + +struct hb_work_private_s +{ + int line; // SSA line number +}; static int decutf8Init(hb_work_object_t *w, hb_job_t *job) { + hb_work_private_t * pv; + pv = calloc( 1, sizeof( hb_work_private_t ) ); + if (pv == NULL) + return 1; + w->private_data = pv; + + // Generate generic SSA Script Info. + int height = job->title->height - job->crop[0] - job->crop[1]; + int width = job->title->width - job->crop[2] - job->crop[3]; + hb_subtitle_add_ssa_header(w->subtitle, width, height); + return 0; } static int decutf8Work(hb_work_object_t * w, hb_buffer_t **buf_in, hb_buffer_t **buf_out) { + hb_work_private_t * pv = w->private_data; // Pass the packets through without modification hb_buffer_t *out = *buf_in; @@ -40,22 +58,19 @@ static int decutf8Work(hb_work_object_t * w, hb_log("decutf8sub: subtitle packet lacks duration"); } - // We shouldn't be storing the extra NULL character, - // but the MP4 muxer expects this, unfortunately. - if (out->size > 0 && out->data[out->size - 1] != '\0') - { - hb_buffer_realloc(out, ++out->size); - out->data[out->size - 1] = '\0'; - } + hb_srt_to_ssa(out, ++pv->line); *buf_in = NULL; *buf_out = out; + + if (out->size == 0) + return HB_WORK_DONE; return HB_WORK_OK; } static void decutf8Close(hb_work_object_t *w) { - // nothing + free(w->private_data); } hb_work_object_t hb_decutf8sub = diff --git a/libhb/muxavformat.c b/libhb/muxavformat.c index 478991363..de8680e17 100644 --- a/libhb/muxavformat.c +++ b/libhb/muxavformat.c @@ -672,6 +672,11 @@ static int avformatInit( hb_mux_object_t * m ) track->st->codec->codec_id = AV_CODEC_ID_HDMV_PGS_SUBTITLE; } break; + case CC608SUB: + case CC708SUB: + case TX3GSUB: + case SRTSUB: + case UTF8SUB: case SSASUB: { if (job->mux == HB_MUX_AV_MP4) @@ -697,18 +702,6 @@ static int avformatInit( hb_mux_object_t * m ) } } break; - case CC608SUB: - case CC708SUB: - case UTF8SUB: - case TX3GSUB: - case SRTSUB: - { - if (job->mux == HB_MUX_AV_MP4) - track->st->codec->codec_id = AV_CODEC_ID_MOV_TEXT; - else - track->st->codec->codec_id = AV_CODEC_ID_TEXT; - } break; - default: continue; } @@ -974,7 +967,7 @@ static int avformatMux(hb_mux_object_t *m, hb_mux_data_t *track, hb_buffer_t *bu AVPacket pkt; int64_t dts, pts, duration = AV_NOPTS_VALUE; hb_job_t *job = m->job; - uint8_t tx3g_out[2048]; + uint8_t sub_out[2048]; if (m->delay == AV_NOPTS_VALUE) { @@ -1129,8 +1122,7 @@ static int avformatMux(hb_mux_object_t *m, hb_mux_data_t *track, hb_buffer_t *bu } track->duration = pts; } - if (track->st->codec->codec_id == AV_CODEC_ID_MOV_TEXT || - track->st->codec->codec_id == AV_CODEC_ID_TEXT) + if (track->st->codec->codec_id == AV_CODEC_ID_MOV_TEXT) { uint8_t styleatom[2048];; uint16_t stylesize = 0; @@ -1150,14 +1142,48 @@ static int avformatMux(hb_mux_object_t *m, hb_mux_data_t *track, hb_buffer_t *bu buffersize = strlen((char*)buffer); /* Write the subtitle sample */ - memcpy( tx3g_out + 2, buffer, buffersize ); - memcpy( tx3g_out + 2 + buffersize, styleatom, stylesize); - tx3g_out[0] = ( buffersize >> 8 ) & 0xff; - tx3g_out[1] = buffersize & 0xff; - pkt.data = tx3g_out; + memcpy( sub_out + 2, buffer, buffersize ); + memcpy( sub_out + 2 + buffersize, styleatom, stylesize); + sub_out[0] = ( buffersize >> 8 ) & 0xff; + sub_out[1] = buffersize & 0xff; + pkt.data = sub_out; pkt.size = buffersize + stylesize + 2; } } + if (track->st->codec->codec_id == AV_CODEC_ID_SSA && + job->mux == HB_MUX_AV_MKV) + { + // avformat requires the this additional information + // which it parses and then strips away + int start_hh, start_mm, stop_hh, stop_mm, layer; + float start_ss, stop_ss; + char *ssa; + + start_hh = buf->s.start / (90000 * 60 * 60); + start_mm = (buf->s.start / (90000 * 60)); + start_ss = ((float)buf->s.start / 90000) - start_mm * 60; + start_mm %= 60; + stop_hh = buf->s.stop / (90000 * 60 * 60); + stop_mm = (buf->s.stop / (90000 * 60)); + stop_ss = ((float)buf->s.stop / 90000) - stop_mm * 60; + stop_mm %= 60; + + // Skip the read-order field + ssa = strchr((char*)buf->data, ','); + if (ssa != NULL) + ssa++; + // Skip the layer field + layer = strtol(ssa, NULL, 10); + ssa = strchr(ssa, ','); + if (ssa != NULL) + ssa++; + sprintf((char*)sub_out, + "Dialogue: %d,%d:%02d:%05.2f,%d:%02d:%05.2f,%s", layer, + start_hh, start_mm, start_ss, + stop_hh, stop_mm, stop_ss, ssa); + pkt.data = sub_out; + pkt.size = strlen((char*)sub_out) + 1; + } pkt.convergence_duration = pkt.duration; } break; diff --git a/libhb/muxcommon.c b/libhb/muxcommon.c index 4738bfa2a..68ba07d21 100644 --- a/libhb/muxcommon.c +++ b/libhb/muxcommon.c @@ -7,6 +7,7 @@ For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html */ #include "hb.h" +#include "decssasub.h" #define MIN_BUFFERING (1024*1024*10) #define MAX_BUFFERING (1024*1024*50) @@ -47,9 +48,9 @@ typedef struct uint32_t allEof; // valid bits in eof (all tracks) uint32_t allRdy; // valid bits in rdy (audio & video tracks) hb_track_t * track[32]; // array of tracks to mux ('ntrack' elements) - // NOTE- this array could be dynamically - // allocated but the eof & rdy logic has to - // be changed to handle more than 32 tracks + // NOTE- this array could be dynamically + // allocated but the eof & rdy logic has to + // be changed to handle more than 32 tracks // anyway so we keep it simple and fast. int buffered_size; } hb_mux_t; @@ -256,9 +257,9 @@ static int muxWork( hb_work_object_t * w, hb_buffer_t ** buf_in, int more = mux->rdy; // all tracks have at least 'interleave' ticks of data. Output // all that we can in 'interleave' size chunks. - while ( (( mux->rdy & mux->allRdy ) == mux->allRdy && + while ( (( mux->rdy & mux->allRdy ) == mux->allRdy && more && mux->buffered_size > MIN_BUFFERING ) || - ( mux->eof == mux->allEof ) ) + ( mux->eof == mux->allEof ) ) { more = 0; for ( i = 0; i < mux->ntracks; ++i ) @@ -380,7 +381,7 @@ void muxClose( hb_work_object_t * w ) } } } - + for( i = 0; i < mux->ntracks; ++i ) { hb_buffer_t * b; @@ -555,245 +556,154 @@ hb_work_object_t hb_muxer = muxClose }; -typedef struct stylerecord_s { - enum style_s {ITALIC, BOLD, UNDERLINE} style; - uint16_t start; - uint16_t stop; - struct stylerecord_s *next; -} stylerecord; +#define TX3G_STYLES (HB_STYLE_FLAG_BOLD | \ + HB_STYLE_FLAG_ITALIC | \ + HB_STYLE_FLAG_UNDERLINE) -static void hb_makestylerecord( stylerecord **stack, - enum style_s style, int start ) +typedef struct style_context_s { - stylerecord *record = calloc( sizeof( stylerecord ), 1 ); + uint8_t * style_atoms; + int style_atom_count; + hb_subtitle_style_t current_style; + int style_start; +} style_context_t; - if( record ) - { - record->style = style; - record->start = start; - record->next = *stack; - *stack = record; - } +static void update_style_atoms(style_context_t *ctx, int stop) +{ + uint8_t *style_entry; + uint8_t face = 0; + + style_entry = ctx->style_atoms + 10 + (12 * ctx->style_atom_count); + + if (ctx->current_style.flags & HB_STYLE_FLAG_BOLD) + face |= 1; + if (ctx->current_style.flags & HB_STYLE_FLAG_ITALIC) + face |= 2; + if (ctx->current_style.flags & HB_STYLE_FLAG_UNDERLINE) + face |= 4; + + style_entry[0] = (ctx->style_start >> 8) & 0xff; // startChar + style_entry[1] = ctx->style_start & 0xff; + style_entry[2] = (stop >> 8) & 0xff; // endChar + style_entry[3] = stop & 0xff; + style_entry[4] = 0; // font-ID msb + style_entry[5] = 1; // font-ID lsb + style_entry[6] = face; // face-style-flags + style_entry[7] = 24; // font-size + style_entry[8] = (ctx->current_style.fg_rgb >> 16) & 0xff; // r + style_entry[9] = (ctx->current_style.fg_rgb >> 8) & 0xff; // g + style_entry[10] = (ctx->current_style.fg_rgb) & 0xff; // b + style_entry[11] = ctx->current_style.fg_alpha; // a + + ctx->style_atom_count++; } -static void hb_makestyleatom( stylerecord *record, uint8_t *style) +static void update_style(style_context_t *ctx, + hb_subtitle_style_t *style, int pos) { - uint8_t face = 1; - hb_deep_log(3, "Made style '%s' from %d to %d", - record->style == ITALIC ? "Italic" : record->style == BOLD ? "Bold" : "Underline", record->start, record->stop); - - switch( record->style ) + if (ctx->style_start < pos) { - case ITALIC: - face = 2; - break; - case BOLD: - face = 1; - break; - case UNDERLINE: - face = 4; - break; - default: - face = 2; - break; + // do we need to add a style atom? + if (((ctx->current_style.flags ^ style->flags) & TX3G_STYLES) || + ctx->current_style.fg_rgb != style->fg_rgb || + ctx->current_style.fg_alpha != style->fg_alpha) + { + update_style_atoms(ctx, pos - 1); + } } + ctx->current_style = *style; + ctx->style_start = pos; +} - style[0] = (record->start >> 8) & 0xff; // startChar - style[1] = record->start & 0xff; - style[2] = (record->stop >> 8) & 0xff; // endChar - style[3] = record->stop & 0xff; - style[4] = (1 >> 8) & 0xff; // font-ID - style[5] = 1 & 0xff; - style[6] = face; // face-style-flags: 1 bold; 2 italic; 4 underline - style[7] = 24; // font-size - style[8] = 255; // r - style[9] = 255; // g - style[10] = 255; // b - style[11] = 255; // a +static void style_context_init(style_context_t *ctx, uint8_t *style_atoms) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->style_atoms = style_atoms; + ctx->style_start = INT_MAX; } /* * Copy the input to output removing markup and adding markup to the style * atom where appropriate. */ -void hb_muxmp4_process_subtitle_style( uint8_t *input, - uint8_t *output, - uint8_t *style, uint16_t *stylesize ) +void hb_muxmp4_process_subtitle_style(uint8_t *input, + uint8_t *output, + uint8_t *style_atoms, uint16_t *stylesize) { - uint8_t *reader = input; - uint8_t *writer = output; - uint8_t stylecount = 0; uint16_t utf8_count = 0; // utf8 count from start of subtitle - stylerecord *stylestack = NULL; - stylerecord *oldrecord = NULL; + int consumed, in_pos = 0, out_pos = 0, len, ii, lines; + style_context_t ctx; + hb_subtitle_style_t style; + char *text, *tmp; + + *stylesize = 0; + style_context_init(&ctx, style_atoms); - while(*reader != '\0') { - if( ( *reader & 0xc0 ) == 0x80 ) + hb_ssa_style_init(&style); + + // Skip past the SSA preamble + text = (char*)input; + for (ii = 0; ii < 8; ii++) + { + tmp = strchr(text, ','); + if (tmp == NULL) + break; + text = tmp + 1; + } + in_pos = text - (char*)input; + + while (input[in_pos] != '\0') + { + lines = 1; + text = hb_ssa_to_text((char*)input + in_pos, &consumed, &style); + if (text == NULL) + break; + + // count UTF8 characters, and get length of text + len = 0; + for (ii = 0; text[ii] != '\0'; ii++) { - /* - * Track the utf8_count when doing markup so that we get the tx3g stops - * based on UTF8 chr counts rather than bytes. - */ - utf8_count++; - hb_deep_log( 3, "MuxMP4: Counted %d UTF-8 chrs within subtitle so far", - utf8_count); - } - if (*reader == '<') { - /* - * possible markup, peek at the next chr - */ - switch(*(reader+1)) { - case 'i': - if (*(reader+2) == '>') { - reader += 3; - hb_makestylerecord(&stylestack, ITALIC, (writer - output - utf8_count)); - } else { - *writer++ = *reader++; - } - break; - case 'b': - if (*(reader+2) == '>') { - reader += 3; - hb_makestylerecord(&stylestack, BOLD, (writer - output - utf8_count)); - } else { - *writer++ = *reader++; - } - break; - case 'u': - if (*(reader+2) == '>') { - reader += 3; - hb_makestylerecord(&stylestack, UNDERLINE, (writer - output - utf8_count)); - } else { - *writer++ = *reader++; - } - break; - case '/': - switch(*(reader+2)) { - case 'i': - if (*(reader+3) == '>') { - /* - * Check whether we then immediately start more markup of the same type, if so then - * lets not close it now and instead continue this markup. - */ - if ((*(reader+4) && *(reader+4) == '<') && - (*(reader+5) && *(reader+5) == 'i') && - (*(reader+6) && *(reader+6) == '>')) { - /* - * Opening italics right after, so don't close off these italics. - */ - hb_deep_log(3, "Joining two sets of italics"); - reader += (4 + 3); - continue; - } - - - if ((*(reader+4) && *(reader+4) == ' ') && - (*(reader+5) && *(reader+5) == '<') && - (*(reader+6) && *(reader+6) == 'i') && - (*(reader+7) && *(reader+7) == '>')) { - /* - * Opening italics right after, so don't close off these italics. - */ - hb_deep_log(3, "Joining two sets of italics (plus space)"); - reader += (4 + 4); - *writer++ = ' '; - continue; - } - if (stylestack && stylestack->style == ITALIC) { - uint8_t style_record[12]; - stylestack->stop = writer - output - utf8_count; - hb_makestyleatom(stylestack, style_record); - - memcpy(style + 10 + (12 * stylecount), style_record, 12); - stylecount++; - - oldrecord = stylestack; - stylestack = stylestack->next; - free(oldrecord); - } else { - hb_error("Mismatched Subtitle markup '%s'", input); - } - reader += 4; - } else { - *writer++ = *reader++; - } - break; - case 'b': - if (*(reader+3) == '>') { - if (stylestack && stylestack->style == BOLD) { - uint8_t style_record[12]; - stylestack->stop = writer - output - utf8_count; - hb_makestyleatom(stylestack, style_record); - - memcpy(style + 10 + (12 * stylecount), style_record, 12); - stylecount++; - oldrecord = stylestack; - stylestack = stylestack->next; - free(oldrecord); - } else { - hb_error("Mismatched Subtitle markup '%s'", input); - } - - reader += 4; - } else { - *writer++ = *reader++; - } - break; - case 'u': - if (*(reader+3) == '>') { - if (stylestack && stylestack->style == UNDERLINE) { - uint8_t style_record[12]; - stylestack->stop = writer - output - utf8_count; - hb_makestyleatom(stylestack, style_record); - - memcpy(style + 10 + (12 * stylecount), style_record, 12); - stylecount++; - - oldrecord = stylestack; - stylestack = stylestack->next; - free(oldrecord); - } else { - hb_error("Mismatched Subtitle markup '%s'", input); - } - reader += 4; - } else { - *writer++ = *reader++; - } - break; - default: - *writer++ = *reader++; - break; - } - break; - default: - *writer++ = *reader++; - break; + if ((text[ii] & 0xc0) == 0x80) + { + utf8_count++; + hb_deep_log( 3, "mux: Counted %d UTF-8 chrs within subtitle", + utf8_count); } - } else if (*reader == '\r') { - // skip '\r' and replace with '\n' if necessary - if (*(++reader) != '\n') { - *writer++ = '\n'; + // By default tx3g only supports 2 lines of text + // To support more lines, we must enable the virtical placement + // flag in the tx3g atom and add tbox atoms to the sample + // data to set the vertical placement for each subtitle. + // Although tbox defines a rectangle, the QT spec says + // that only the vertical placement is honored (bummer). + if (text[ii] == '\n') + { + lines++; + if (lines > 2) + text[ii] = ' '; } - } else { - *writer++ = *reader++; + len++; } + strcpy((char*)output+out_pos, text); + free(text); + out_pos += len; + in_pos += consumed; + update_style(&ctx, &style, out_pos - utf8_count); } - *writer = '\0'; + // null terminate output string + output[out_pos] = 0; - if( stylecount ) + if (ctx.style_atom_count > 0) { - *stylesize = 10 + ( stylecount * 12 ); + *stylesize = 10 + (ctx.style_atom_count * 12); - memcpy( style + 4, "styl", 4); - - style[0] = 0; - style[1] = 0; - style[2] = (*stylesize >> 8) & 0xff; - style[3] = *stylesize & 0xff; - style[8] = (stylecount >> 8) & 0xff; - style[9] = stylecount & 0xff; + memcpy(style_atoms + 4, "styl", 4); + style_atoms[0] = 0; + style_atoms[1] = 0; + style_atoms[2] = (*stylesize >> 8) & 0xff; + style_atoms[3] = *stylesize & 0xff; + style_atoms[8] = (ctx.style_atom_count >> 8) & 0xff; + style_atoms[9] = ctx.style_atom_count & 0xff; } - } diff --git a/libhb/muxmkv.c b/libhb/muxmkv.c index 0c2a28490..1250a681e 100644 --- a/libhb/muxmkv.c +++ b/libhb/muxmkv.c @@ -410,21 +410,17 @@ static int MKVInit( hb_mux_object_t * m ) track->codecPrivateSize = 0; track->codecID = MK_SUBTITLE_PGS; break; + case CC608SUB: + case CC708SUB: + case TX3GSUB: + case UTF8SUB: + case SRTSUB: case SSASUB: track->codecID = MK_SUBTITLE_SSA; need_fonts = 1; track->codecPrivate = subtitle->extradata; track->codecPrivateSize = subtitle->extradata_size; break; - case CC608SUB: - case CC708SUB: - case UTF8SUB: - case TX3GSUB: - case SRTSUB: - track->codecPrivate = NULL; - track->codecPrivateSize = 0; - track->codecID = MK_SUBTITLE_UTF8; - break; default: continue; } diff --git a/libhb/rendersub.c b/libhb/rendersub.c index eaec996c6..6d0772f22 100644 --- a/libhb/rendersub.c +++ b/libhb/rendersub.c @@ -9,7 +9,6 @@ #include "hb.h" #include "hbffmpeg.h" -#include "colormap.h" #include <ass/ass.h> struct hb_filter_private_s @@ -25,15 +24,13 @@ struct hb_filter_private_s ASS_Library * ssa; ASS_Renderer * renderer; ASS_Track * ssaTrack; + uint8_t script_initialized; // SRT int line; hb_buffer_t * current_sub; }; -static char* srt_markup_to_ssa(char *srt, int *len); -static void srt_to_ssa(hb_buffer_t *srt_sub); - // VOBSUB static int vobsub_init( hb_filter_object_t * filter, hb_filter_init_t * init ); @@ -512,10 +509,6 @@ static int ssa_init( hb_filter_object_t * filter, return 1; } - // NOTE: The codec extradata is expected to be in MKV format - ass_process_codec_private( pv->ssaTrack, - (char *)filter->subtitle->extradata, filter->subtitle->extradata_size ); - int width = init->width - ( pv->crop[2] + pv->crop[3] ); int height = init->height - ( pv->crop[0] + pv->crop[1] ); ass_set_frame_size( pv->renderer, width, height); @@ -554,6 +547,19 @@ static int ssa_work( hb_filter_object_t * filter, hb_buffer_t * in = *buf_in; hb_buffer_t * sub; + if (!pv->script_initialized) + { + // NOTE: The codec extradata is expected to be in MKV format + // I would like to initialize this in ssa_init, but when we are + // transcoding text subtitles to SSA, the extradata does not + // get initialized until the decoder is initialized. Since + // decoder initialization happens after filter initialization, + // we need to postpone this. + ass_process_codec_private(pv->ssaTrack, + (char*)filter->subtitle->extradata, + filter->subtitle->extradata_size); + pv->script_initialized = 1; + } if ( in->size <= 0 ) { *buf_in = NULL; @@ -584,28 +590,15 @@ static int ssa_work( hb_filter_object_t * filter, static int textsub_init( hb_filter_object_t * filter, hb_filter_init_t * init ) { - const char * ssa_header = - "[Script Info]\r\n" - "ScriptType: v4.00+\r\n" - "Collisions: Normal\r\n" - "PlayResX: 1920\r\n" - "PlayResY: 1080\r\n" - "Timer: 100.0\r\n" - "WrapStyle: 0\r\n" - "\r\n" - "[V4+ Styles]\r\n" - "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\r\n" - "Style: Default,Arial,72,&H00FFFFFF,&H00FFFFFF,&H000F0F0F,&H000F0F0F,0,0,0,0,100,100,0,0.00,1,2,3,2,20,20,20,0\r\n"; - - filter->subtitle->extradata = (uint8_t*)ssa_header; - filter->subtitle->extradata_size = strlen(ssa_header); - - int result = ssa_init(filter, init); - - filter->subtitle->extradata = NULL; - filter->subtitle->extradata_size = 0; - - return result; + hb_filter_private_t * pv = filter->private_data; + + int width = init->width - pv->crop[2] - pv->crop[3]; + int height = init->height - pv->crop[0] - pv->crop[1]; + + // Text subtitles for which we create a dummy ASS header need + // to have the header rewritten with the correct dimensions. + hb_subtitle_add_ssa_header(filter->subtitle, width, height); + return ssa_init(filter, init); } static void textsub_close( hb_filter_object_t * filter ) @@ -615,20 +608,24 @@ static void textsub_close( hb_filter_object_t * filter ) static void process_sub(hb_filter_private_t *pv, hb_buffer_t *sub) { - char *ssa; - int len; - int64_t start, duration; + int64_t start, dur; + char *ssa, *tmp; + + // libass expects every chunk to have a unique sequence number + // since we are repeating subs in some cases, we need to replace + // the sequence number. + tmp = strchr((char*)sub->data, ','); + if (tmp == NULL) + return; - // Create SSA entry - ssa = hb_strdup_printf("%d,,Default,,0,0,0,,%s", ++pv->line, sub->data); - len = strlen(ssa); + ssa = hb_strdup_printf("%d%s", ++pv->line, tmp); // Parse MKV-SSA packet // SSA subtitles always have an explicit stop time, so we // do not need to do special processing for stop == AV_NOPTS_VALUE start = sub->s.start / 90; - duration = (sub->s.stop - sub->s.start) / 90; - ass_process_chunk( pv->ssaTrack, ssa, len, start, duration); + dur = (sub->s.stop - sub->s.start) / 90; + ass_process_chunk(pv->ssaTrack, ssa, sub->size, start, dur); free(ssa); } @@ -640,6 +637,14 @@ static int textsub_work(hb_filter_object_t * filter, hb_buffer_t * in = *buf_in; hb_buffer_t * sub; + if (!pv->script_initialized) + { + ass_process_codec_private(pv->ssaTrack, + (char*)filter->subtitle->extradata, + filter->subtitle->extradata_size); + pv->script_initialized = 1; + } + if (in->size <= 0) { *buf_in = NULL; @@ -651,17 +656,6 @@ static int textsub_work(hb_filter_object_t * filter, // subtitle list while ((sub = hb_fifo_get(filter->subtitle->fifo_out))) { - switch (pv->type) - { - case CC608SUB: - case SRTSUB: - case UTF8SUB: - case TX3GSUB: - srt_to_ssa(sub); - break; - default: - break; - } // Subtitle formats such as CC can have stop times // that are not known until an "erase display" command // is encountered in the stream. For these formats @@ -961,114 +955,3 @@ static void hb_rendersub_close( hb_filter_object_t * filter ) } } -static char* srt_markup_to_ssa(char *srt, int *len) -{ - char terminator; - char color[40]; - uint32_t rgb; - - *len = 0; - if (srt[0] != '<' && srt[0] != '{') - return NULL; - - if (srt[0] == '<') - terminator = '>'; - else - terminator = '}'; - - if (srt[1] == 'i' && srt[2] == terminator) - { - *len = 3; - return hb_strdup_printf("{\\i1}"); - } - else if (srt[1] == 'b' && srt[2] == terminator) - { - *len = 3; - return hb_strdup_printf("{\\b1}"); - } - else if (srt[1] == 'u' && srt[2] == terminator) - { - *len = 3; - return hb_strdup_printf("{\\u1}"); - } - else if (srt[1] == '/' && srt[2] == 'i' && srt[3] == terminator) - { - *len = 4; - return hb_strdup_printf("{\\i0}"); - } - else if (srt[1] == '/' && srt[2] == 'b' && srt[3] == terminator) - { - *len = 4; - return hb_strdup_printf("{\\b0}"); - } - else if (srt[1] == '/' && srt[2] == 'u' && srt[3] == terminator) - { - *len = 4; - return hb_strdup_printf("{\\u0}"); - } - else if (srt[0] == '<' && !strncmp(srt + 1, "font", 4)) - { - int match; - match = sscanf(srt + 1, "font color=\"%39[^\"]\">", color); - if (match != 1) - { - return NULL; - } - while (srt[*len] != '>') (*len)++; - (*len)++; - if (color[0] == '#') - rgb = strtol(color + 1, NULL, 16); - else - rgb = hb_rgb_lookup_by_name(color); - return hb_strdup_printf("{\\1c&H%X&}", HB_RGB_TO_BGR(rgb)); - } - else if (srt[0] == '<' && srt[1] == '/' && !strncmp(srt + 2, "font", 4) && - srt[6] == '>') - { - *len = 7; - return hb_strdup_printf("{\\1c&HFFFFFF&}"); - } - - return NULL; -} - -static void srt_to_ssa(hb_buffer_t *sub_in) -{ - char * srt = (char*)sub_in->data; - // SSA markup expands a little over SRT, so allocate a bit of extra - // space. More will be realloc'd if needed. - hb_buffer_t * sub = hb_buffer_init(sub_in->size + 20); - char * ssa, *ssa_markup; - int skip, len, pos, ii; - - // Exchange data between input sub and new ssa_sub - // After this, sub_in contains ssa data - hb_buffer_swap_copy(sub_in, sub); - ssa = (char*)sub_in->data; - pos = 0; - ii = 0; - while (srt[ii] != '\0') - { - if ((ssa_markup = srt_markup_to_ssa(srt + ii, &skip)) != NULL) - { - len = strlen(ssa_markup); - hb_buffer_realloc(sub_in, pos + len + 1); - // After realloc, sub_in->data may change - ssa = (char*)sub_in->data; - snprintf(ssa + pos, len + 1, "%s", ssa_markup); - free(ssa_markup); - pos += len; - ii += skip; - } - else - { - hb_buffer_realloc(sub_in, pos + 2); - // After realloc, sub_in->data may change - ssa = (char*)sub_in->data; - ssa[pos++] = srt[ii++]; - } - } - ssa[pos] = '\0'; - hb_buffer_close(&sub); -} - |