diff options
-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); -} - |