From 5c5aaf3564f95386860c5a7fd66993cd85d06751 Mon Sep 17 00:00:00 2001 From: eddyg Date: Mon, 4 May 2009 04:56:19 +0000 Subject: [PATCH] Soft Subs Part 2: Auto-detect CC during scan, add CC to subtitle list in title, if selected then CC's are extracted, MP4 Muxer will dump the subs to the log at the end of encoding. TODO: Translate PTS for buf->start to HB format, add MP4 subtitle track and subs during muxing. git-svn-id: svn://localhost/HandBrake/trunk@2375 b64f7644-9d1e-0410-96f1-a4d463321fa5 --- libhb/common.h | 5 +- libhb/deccc608sub.c | 2403 +++++++++++++++++++++++++++++++++++++++ libhb/deccc608sub.h | 131 +++ libhb/decmpeg2.c | 239 +++- libhb/{decsub.c => decvobsub.c} | 0 libhb/muxcommon.c | 52 +- libhb/muxmp4.c | 32 + libhb/scan.c | 1 + libhb/work.c | 7 +- test/test.c | 4 +- 10 files changed, 2847 insertions(+), 27 deletions(-) create mode 100644 libhb/deccc608sub.c create mode 100644 libhb/deccc608sub.h rename libhb/{decsub.c => decvobsub.c} (100%) diff --git a/libhb/common.h b/libhb/common.h index f1951102..6a2c8d51 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -634,7 +634,7 @@ struct hb_work_object_s hb_esconfig_t * config; /* Pointer hb_audio_t so we have access to the info in the audio worker threads. */ - hb_audio_t *audio; + hb_audio_t * audio; hb_work_private_t * private_data; @@ -642,9 +642,10 @@ struct hb_work_object_s volatile int * done; int status; int codec_param; + hb_title_t * title; hb_work_object_t * next; - int thread_sleep_interval; + int thread_sleep_interval; #endif }; diff --git a/libhb/deccc608sub.c b/libhb/deccc608sub.c new file mode 100644 index 00000000..2b0b199f --- /dev/null +++ b/libhb/deccc608sub.c @@ -0,0 +1,2403 @@ +/* + * 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. + */ +#include "hb.h" +#include "deccc608sub.h" + +/* + * ccextractor static configuration variables. + */ +static int debug_608 = 0; +static int trim_subs = 0; +static int nofontcolor = 0; +static enum encoding_type encoding = ENC_UTF_8; +static int cc_channel = 1; +static enum output_format write_format = OF_TRANSCRIPT; +static int sentence_cap = 1; +static int subs_delay = 0; +static LLONG screens_to_process = -1; +static int processed_enough = 0; +static int gui_mode_reports = 0; +static int norollup = 1; +static int direct_rollup = 0; + +static LLONG get_fts(void) +{ + return 0; +} + +#define fatal(N, ...) // N +#define XMLRPC_APPEND(N, ...) // N + +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 *enc_buffer=NULL; // Generic general purpose buffer +unsigned char str[2048]; // Another generic general purpose buffer +unsigned enc_buffer_used; +unsigned enc_buffer_capacity; + +#define GUARANTEE(length) if (length>enc_buffer_capacity) \ +{enc_buffer_capacity*=2; enc_buffer=(unsigned char*) realloc (enc_buffer, enc_buffer_capacity); \ + if (enc_buffer==NULL) { fatal (EXIT_NOT_ENOUGH_MEMORY, "Not enough memory, bailing out\n"); } \ +} + +const unsigned char pac2_attribs[][3]= // Color, font, ident +{ + {COL_WHITE, FONT_REGULAR, 0}, // 0x40 || 0x60 + {COL_WHITE, FONT_UNDERLINED, 0}, // 0x41 || 0x61 + {COL_GREEN, FONT_REGULAR, 0}, // 0x42 || 0x62 + {COL_GREEN, FONT_UNDERLINED, 0}, // 0x43 || 0x63 + {COL_BLUE, FONT_REGULAR, 0}, // 0x44 || 0x64 + {COL_BLUE, FONT_UNDERLINED, 0}, // 0x45 || 0x65 + {COL_CYAN, FONT_REGULAR, 0}, // 0x46 || 0x66 + {COL_CYAN, FONT_UNDERLINED, 0}, // 0x47 || 0x67 + {COL_RED, FONT_REGULAR, 0}, // 0x48 || 0x68 + {COL_RED, FONT_UNDERLINED, 0}, // 0x49 || 0x69 + {COL_YELLOW, FONT_REGULAR, 0}, // 0x4a || 0x6a + {COL_YELLOW, FONT_UNDERLINED, 0}, // 0x4b || 0x6b + {COL_MAGENTA, FONT_REGULAR, 0}, // 0x4c || 0x6c + {COL_MAGENTA, FONT_UNDERLINED, 0}, // 0x4d || 0x6d + {COL_WHITE, FONT_ITALICS, 0}, // 0x4e || 0x6e + {COL_WHITE, FONT_UNDERLINED_ITALICS, 0}, // 0x4f || 0x6f + {COL_WHITE, FONT_REGULAR, 0}, // 0x50 || 0x70 + {COL_WHITE, FONT_UNDERLINED, 0}, // 0x51 || 0x71 + {COL_WHITE, FONT_REGULAR, 4}, // 0x52 || 0x72 + {COL_WHITE, FONT_UNDERLINED, 4}, // 0x53 || 0x73 + {COL_WHITE, FONT_REGULAR, 8}, // 0x54 || 0x74 + {COL_WHITE, FONT_UNDERLINED, 8}, // 0x55 || 0x75 + {COL_WHITE, FONT_REGULAR, 12}, // 0x56 || 0x76 + {COL_WHITE, FONT_UNDERLINED, 12}, // 0x57 || 0x77 + {COL_WHITE, FONT_REGULAR, 16}, // 0x58 || 0x78 + {COL_WHITE, FONT_UNDERLINED, 16}, // 0x59 || 0x79 + {COL_WHITE, FONT_REGULAR, 20}, // 0x5a || 0x7a + {COL_WHITE, FONT_UNDERLINED, 20}, // 0x5b || 0x7b + {COL_WHITE, FONT_REGULAR, 24}, // 0x5c || 0x7c + {COL_WHITE, FONT_UNDERLINED, 24}, // 0x5d || 0x7d + {COL_WHITE, FONT_REGULAR, 28}, // 0x5e || 0x7e + {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; + +const char *sami_header= // TODO: Revise the \n\ +\n\ +\n\n\ +\n"; + +const char *command_type[] = +{ + "Unknown", + "EDM - EraseDisplayedMemory", + "RCL - ResumeCaptionLoading", + "EOC - End Of Caption", + "TO1 - Tab Offset, 1 column", + "TO2 - Tab Offset, 2 column", + "TO3 - Tab Offset, 3 column", + "RU2 - Roll up 2 rows", + "RU3 - Roll up 3 rows", + "RU4 - Roll up 4 rows", + "CR - Carriage Return", + "ENM - Erase non-displayed memory", + "BS - Backspace", + "RTD - Resume Text Display" +}; + +const char *font_text[]= +{ + "regular", + "italics", + "underlined", + "underlined italics" +}; + +const char *cc_modes_text[]= +{ + "Pop-Up captions" +}; + +const char *color_text[][2]= +{ + {"white",""}, + {"green",""}, + {"blue",""}, + {"cyan",""}, + {"red",""}, + {"yellow",""}, + {"magenta",""}, + {"userdefined","subline) { + wb->subline = malloc(2048); + + if (!wb->subline) + { + return -1; + } + } + + wb->new_sentence = 1; + wb->new_channel = 1; + wb->in_xds_mode = 0; + return 0; +} + +/* + * Free up CC memory - don't call this from HB just yet since it will cause + * 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) +{ + if( enc_buffer ) { + free(enc_buffer); + enc_buffer_capacity = 0; + enc_buffer_used = 0; + } + if( wb->subline ) { + free(wb->subline); + } +} + + +#include + +void get_char_in_latin_1 (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<0x80) // Regular line-21 character set, mostly ASCII except these exceptions + { + switch (c) + { + case 0x2a: // lowercase a, acute accent + *buffer=0xc3; + *(buffer+1)=0xa1; + return 2; + case 0x5c: // lowercase e, acute accent + *buffer=0xc3; + *(buffer+1)=0xa9; + return 2; + case 0x5e: // lowercase i, acute accent + *buffer=0xc3; + *(buffer+1)=0xad; + return 2; + case 0x5f: // lowercase o, acute accent + *buffer=0xc3; + *(buffer+1)=0xb3; + return 2; + case 0x60: // lowercase u, acute accent + *buffer=0xc3; + *(buffer+1)=0xba; + return 2; + case 0x7b: // lowercase c with cedilla + *buffer=0xc3; + *(buffer+1)=0xa7; + return 2; + case 0x7c: // division symbol + *buffer=0xc3; + *(buffer+1)=0xb7; + return 2; + case 0x7d: // uppercase N tilde + *buffer=0xc3; + *(buffer+1)=0x91; + return 2; + case 0x7e: // lowercase n tilde + *buffer=0xc3; + *(buffer+1)=0xb1; + return 2; + default: + *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 + case 0x80: // Registered symbol (R) + *buffer=0xc2; + *(buffer+1)=0xae; + return 2; + case 0x81: // degree sign + *buffer=0xc2; + *(buffer+1)=0xb0; + return 2; + case 0x82: // 1/2 symbol + *buffer=0xc2; + *(buffer+1)=0xbd; + return 2; + case 0x83: // Inverted (open) question mark + *buffer=0xc2; + *(buffer+1)=0xbf; + return 2; + case 0x84: // Trademark symbol (TM) + *buffer=0xe2; + *(buffer+1)=0x84; + *(buffer+2)=0xa2; + return 3; + case 0x85: // Cents symbol + *buffer=0xc2; + *(buffer+1)=0xa2; + return 2; + case 0x86: // Pounds sterling + *buffer=0xc2; + *(buffer+1)=0xa3; + return 2; + case 0x87: // Music note + *buffer=0xe2; + *(buffer+1)=0x99; + *(buffer+2)=0xaa; + return 3; + case 0x88: // lowercase a, grave accent + *buffer=0xc3; + *(buffer+1)=0xa0; + return 2; + case 0x89: // transparent space, we make it regular + *buffer=0x20; + return 1; + case 0x8a: // lowercase e, grave accent + *buffer=0xc3; + *(buffer+1)=0xa8; + return 2; + case 0x8b: // lowercase a, circumflex accent + *buffer=0xc3; + *(buffer+1)=0xa2; + return 2; + case 0x8c: // lowercase e, circumflex accent + *buffer=0xc3; + *(buffer+1)=0xaa; + return 2; + case 0x8d: // lowercase i, circumflex accent + *buffer=0xc3; + *(buffer+1)=0xae; + return 2; + case 0x8e: // lowercase o, circumflex accent + *buffer=0xc3; + *(buffer+1)=0xb4; + return 2; + case 0x8f: // lowercase u, circumflex accent + *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 + case 0x90: // capital letter A with acute + *buffer=0xc3; + *(buffer+1)=0x81; + return 2; + case 0x91: // capital letter E with acute + *buffer=0xc3; + *(buffer+1)=0x89; + return 2; + case 0x92: // capital letter O with acute + *buffer=0xc3; + *(buffer+1)=0x93; + return 2; + case 0x93: // capital letter U with acute + *buffer=0xc3; + *(buffer+1)=0x9a; + return 2; + case 0x94: // capital letter U with diaresis + *buffer=0xc3; + *(buffer+1)=0x9c; + return 2; + case 0x95: // lowercase letter U with diaeresis + *buffer=0xc3; + *(buffer+1)=0xbc; + return 2; + case 0x96: // apostrophe + *buffer=0x27; + return 1; + case 0x97: // inverted exclamation mark + *buffer=0xc1; + *(buffer+1)=0xa1; + return 2; + case 0x98: // asterisk + *buffer=0x2a; + return 1; + case 0x99: // apostrophe (yes, duped). See CCADI source code. + *buffer=0x27; + return 1; + case 0x9a: // hyphen-minus + *buffer=0x2d; + return 1; + case 0x9b: // copyright sign + *buffer=0xc2; + *(buffer+1)=0xa9; + return 2; + case 0x9c: // Service mark + *buffer=0xe2; + *(buffer+1)=0x84; + *(buffer+2)=0xa0; + return 3; + case 0x9d: // Full stop (.) + *buffer=0x2e; + return 1; + case 0x9e: // Quoatation mark + *buffer=0x22; + return 1; + case 0x9f: // Quoatation mark + *buffer=0x22; + return 1; + case 0xa0: // uppercase A, grave accent + *buffer=0xc3; + *(buffer+1)=0x80; + return 2; + case 0xa1: // uppercase A, circumflex + *buffer=0xc3; + *(buffer+1)=0x82; + return 2; + case 0xa2: // uppercase C with cedilla + *buffer=0xc3; + *(buffer+1)=0x87; + return 2; + case 0xa3: // uppercase E, grave accent + *buffer=0xc3; + *(buffer+1)=0x88; + return 2; + case 0xa4: // uppercase E, circumflex + *buffer=0xc3; + *(buffer+1)=0x8a; + return 2; + case 0xa5: // capital letter E with diaresis + *buffer=0xc3; + *(buffer+1)=0x8b; + return 2; + case 0xa6: // lowercase letter e with diaresis + *buffer=0xc3; + *(buffer+1)=0xab; + return 2; + case 0xa7: // uppercase I, circumflex + *buffer=0xc3; + *(buffer+1)=0x8e; + return 2; + case 0xa8: // uppercase I, with diaresis + *buffer=0xc3; + *(buffer+1)=0x8f; + return 2; + case 0xa9: // lowercase i, with diaresis + *buffer=0xc3; + *(buffer+1)=0xaf; + return 2; + case 0xaa: // uppercase O, circumflex + *buffer=0xc3; + *(buffer+1)=0x94; + return 2; + case 0xab: // uppercase U, grave accent + *buffer=0xc3; + *(buffer+1)=0x99; + return 2; + case 0xac: // lowercase u, grave accent + *buffer=0xc3; + *(buffer+1)=0xb9; + return 2; + case 0xad: // uppercase U, circumflex + *buffer=0xc3; + *(buffer+1)=0x9b; + return 2; + case 0xae: // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + *buffer=0xc2; + *(buffer+1)=0xab; + return 2; + case 0xaf: // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + *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 + case 0xb0: // Uppercase A, tilde + *buffer=0xc3; + *(buffer+1)=0x83; + return 2; + case 0xb1: // Lowercase a, tilde + *buffer=0xc3; + *(buffer+1)=0xa3; + return 2; + case 0xb2: // Uppercase I, acute accent + *buffer=0xc3; + *(buffer+1)=0x8d; + return 2; + case 0xb3: // Uppercase I, grave accent + *buffer=0xc3; + *(buffer+1)=0x8c; + return 2; + case 0xb4: // Lowercase i, grave accent + *buffer=0xc3; + *(buffer+1)=0xac; + return 2; + case 0xb5: // Uppercase O, grave accent + *buffer=0xc3; + *(buffer+1)=0x92; + return 2; + case 0xb6: // Lowercase o, grave accent + *buffer=0xc3; + *(buffer+1)=0xb2; + return 2; + case 0xb7: // Uppercase O, tilde + *buffer=0xc3; + *(buffer+1)=0x95; + return 2; + case 0xb8: // Lowercase o, tilde + *buffer=0xc3; + *(buffer+1)=0xb5; + return 2; + case 0xb9: // Open curly brace + *buffer=0x7b; + return 1; + case 0xba: // Closing curly brace + *buffer=0x7d; + return 1; + case 0xbb: // Backslash + *buffer=0x5c; + return 1; + case 0xbc: // Caret + *buffer=0x5e; + return 1; + case 0xbd: // Underscore + *buffer=0x5f; + return 1; + case 0xbe: // Pipe (broken bar) + *buffer=0xc2; + *(buffer+1)=0xa6; + return 1; + case 0xbf: // Tilde + *buffer=0x7e; // Not sure + return 1; + case 0xc0: // Uppercase A, umlaut + *buffer=0xc3; + *(buffer+1)=0x84; + return 2; + case 0xc1: // Lowercase A, umlaut + *buffer=0xc3; + *(buffer+1)=0xa4; + return 2; + case 0xc2: // Uppercase O, umlaut + *buffer=0xc3; + *(buffer+1)=0x96; + return 2; + case 0xc3: // Lowercase o, umlaut + *buffer=0xc3; + *(buffer+1)=0xb6; + return 2; + case 0xc4: // Esszett (sharp S) + *buffer=0xc3; + *(buffer+1)=0x9f; + return 2; + case 0xc5: // Yen symbol + *buffer=0xc2; + *(buffer+1)=0xa5; + return 2; + case 0xc6: // Currency symbol + *buffer=0xc2; + *(buffer+1)=0xa4; + return 2; + case 0xc7: // Vertical bar + *buffer=0x7c; + return 1; + case 0xc8: // Uppercase A, ring + *buffer=0xc3; + *(buffer+1)=0x85; + return 2; + case 0xc9: // Lowercase A, ring + *buffer=0xc3; + *(buffer+1)=0xa5; + return 2; + case 0xca: // Uppercase O, slash + *buffer=0xc3; + *(buffer+1)=0x98; + return 2; + case 0xcb: // Lowercase o, slash + *buffer=0xc3; + *(buffer+1)=0xb8; + return 2; + case 0xcc: // Upper left corner + *buffer=0xe2; + *(buffer+1)=0x8c; + *(buffer+2)=0x9c; + return 3; + case 0xcd: // Upper right corner + *buffer=0xe2; + *(buffer+1)=0x8c; + *(buffer+2)=0x9d; + return 3; + case 0xce: // Lower left corner + *buffer=0xe2; + *(buffer+1)=0x8c; + *(buffer+2)=0x9e; + return 3; + case 0xcf: // Lower right corner + *buffer=0xe2; + *(buffer+1)=0x8c; + *(buffer+2)=0x9f; + return 3; + default: // + *buffer='?'; // I'll do it eventually, I promise + return 1; // This are weird chars anyway + } +} + +unsigned char cctolower (unsigned char c) +{ + 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; + 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++; + } + 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 (icharacters[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) +{ + int i; + + for (i=0;icharacters[line_num][i]) + { + 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; + } + } +} + +void find_limit_characters (unsigned char *line, int *first_non_blank, int *last_non_blank) +{ + int i; + + *last_non_blank=-1; + *first_non_blank=-1; + for (i=0;icharacters[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); + + if (first_non_blank==-1) + { + *buffer=0; + return 0; + } + + int bytes=0; + for (i=first_non_blank;i<=last_non_blank;i++) + { + char c=line[i]; + switch (encoding) + { + 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; + } + buffer+=bytes; + } + *buffer=0; + return (unsigned) (buffer-orig); // Return length +} + +unsigned get_decoder_line_encoded_for_gui (unsigned char *buffer, int line_num, struct eia608_screen *data) +{ + unsigned char *line = data->characters[line_num]; + unsigned char *orig=buffer; // Keep for debugging + int first=0, last=31; + int i; + + find_limit_characters(line,&first,&last); + for (i=first;i<=last;i++) + { + get_char_in_latin_1 (buffer,line[i]); + buffer++; + } + *buffer=0; + return (unsigned) (buffer-orig); // Return length + +} + +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; + + 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 + { + buffer+= encode_line (buffer,(unsigned char *) ""); + } + // Add new font tag + buffer+=encode_line (buffer, (unsigned char*) color_text[its_col][1]); + if (its_col==COL_USERDEFINED) + { + // The previous sentence doesn't copy the whole + // tag, just up to the quote before the color + buffer+=encode_line (buffer, (unsigned char*) usercolor_rgb); + buffer+=encode_line (buffer, (unsigned char*) "\">"); + } + + 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 *) ""); + } + if (is_underlined==0 && underlined) // Close underline + { + buffer+=encode_line (buffer, (unsigned char *) ""); + } + 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 *) ""); + } + if (has_ita==0 && italics) // Close italics + { + buffer+=encode_line (buffer, (unsigned char *) ""); + } + 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; + } + buffer+=bytes; + } + if (italics) + { + buffer+=encode_line (buffer, (unsigned char *) ""); + } + if (underlined) + { + buffer+=encode_line (buffer, (unsigned char *) ""); + } + if (col != COL_WHITE && !nofontcolor) + { + buffer+=encode_line (buffer, (unsigned char *) ""); + } + *buffer=0; + return (unsigned) (buffer-orig); // Return length +} + + +void delete_all_lines_but_current (struct eia608_screen *data, int row) +{ + int i; + for (i=0;i<15;i++) + { + if (i!=row) + { + memset(data->characters[i],' ',CC608_SCREEN_WIDTH); + data->characters[i][CC608_SCREEN_WIDTH]=0; + memset (data->colors[i],default_color,CC608_SCREEN_WIDTH+1); + memset (data->fonts[i],FONT_REGULAR,CC608_SCREEN_WIDTH+1); + data->row_used[i]=0; + } + } +} + +void clear_eia608_cc_buffer (struct eia608_screen *data) +{ + int i; + + for (i=0;i<15;i++) + { + memset(data->characters[i],' ',CC608_SCREEN_WIDTH); + data->characters[i][CC608_SCREEN_WIDTH]=0; + memset (data->colors[i],default_color,CC608_SCREEN_WIDTH+1); + memset (data->fonts[i],FONT_REGULAR,CC608_SCREEN_WIDTH+1); + data->row_used[i]=0; + } + data->empty=1; +} + +void init_eia608 (struct eia608 *data) +{ + 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; +} + +struct eia608_screen *get_writing_buffer (struct s_write *wb) +{ + struct eia608_screen *use_buffer=NULL; + switch (wb->data608->mode) + { + case MODE_POPUP: // Write on the non-visible buffer + if (wb->data608->visible_buffer==1) + use_buffer = &wb->data608->buffer2; + else + use_buffer = &wb->data608->buffer1; + break; + case MODE_ROLLUP_2: // Write directly to screen + case MODE_ROLLUP_3: + case MODE_ROLLUP_4: + if (wb->data608->visible_buffer==1) + use_buffer = &wb->data608->buffer1; + else + use_buffer = &wb->data608->buffer2; + break; + default: + fatal (EXIT_BUG_BUG, "Caption mode has an illegal value at get_writing_buffer(), this is a bug.\n"); + } + return use_buffer; +} + +void write_char (const unsigned char c, struct s_write *wb) +{ + if (wb->data608->mode!=MODE_TEXT) + { + 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) + wb->data608->cursor_column++; + } + +} + +/* Handle MID-ROW CODES. */ +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; + if (wb->data608->channel!=cc_channel) + return; + if (debug_608) + hb_log ("\r608: text_attr: %02X %02X",c1,c2); + if ( ((c1!=0x11 && c1!=0x19) || + (c2<0x20 || c2>0x2f)) && debug_608) + { + hb_log ("\rThis is not a text attribute!\n"); + } + else + { + int i = c2-0x20; + 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]); + if (wb->data608->cursor_column<31) + wb->data608->cursor_column++; + } +} + +void mstotime (LLONG milli, unsigned *hours, unsigned *minutes, + unsigned *seconds, unsigned *ms) +{ + // LLONG milli = (LLONG) ((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 write_subtitle_file_footer (struct s_write *wb) +{ + switch (write_format) + { + case OF_SAMI: + sprintf ((char *) str,"\n"); + if (debug_608 && encoding!=ENC_UNICODE) + { + hb_log ("\r%s\n", str); + } + enc_buffer_used=encode_line (enc_buffer,(unsigned char *) str); + //fwrite (enc_buffer,enc_buffer_used,1,wb->fh); + XMLRPC_APPEND(enc_buffer,enc_buffer_used); + break; + default: // Nothing to do. Only SAMI has a footer + break; + } +} + +void fhb_log_encoded (FILE *fh, const char *string) +{ + GUARANTEE(strlen (string)*3); + enc_buffer_used=encode_line (enc_buffer,(unsigned char *) string); + fwrite (enc_buffer,enc_buffer_used,1,fh); +} + +void write_subtitle_file_header (struct s_write *wb) +{ + switch (write_format) + { + case OF_SRT: // Subrip subtitles have no header + break; + case OF_SAMI: // This header brought to you by McPoodle's CCASDI + //fhb_log_encoded (wb->fh, sami_header); + GUARANTEE(strlen (sami_header)*3); + enc_buffer_used=encode_line (enc_buffer,(unsigned char *) sami_header); + //fwrite (enc_buffer,enc_buffer_used,1,wb->fh); + XMLRPC_APPEND(enc_buffer,enc_buffer_used); + break; + case OF_RCWT: // Write header + //fwrite (rcwt_header, sizeof(rcwt_header),1,wb->fh); + break; + case OF_TRANSCRIPT: // No header. Fall thru + default: + break; + } +} + +void write_cc_line_as_transcript (struct eia608_screen *data, struct s_write *wb, int line_number) +{ + hb_buffer_t *buffer; + + if (sentence_cap) + { + capitalize(line_number,data, &wb->new_sentence); + correct_case(line_number,data); + } + int length = get_decoder_line_basic (wb->subline, line_number, data); + if (debug_608 && encoding!=ENC_UNICODE) + { + hb_log ("\r"); + hb_log ("%s\n",wb->subline); + } + if (length>0) + { + //fwrite (wb->subline, 1, length, wb->fh); + /* + * Put this subtitle in a hb_buffer_t and shove it into the subtitle fifo + */ + buffer = hb_buffer_init( strlen( wb->subline ) + 1 ); + buffer->start = wb->last_pts; + buffer->stop = wb->last_pts+1; + strcpy( buffer->data, wb->subline ); + //hb_log("CC %lld: %s", buffer->stop, wb->subline); + + hb_fifo_push( wb->subtitle->fifo_raw, buffer ); + + XMLRPC_APPEND(wb->subline,length); + //fwrite (encoded_crlf, 1, encoded_crlf_length,wb->fh); + XMLRPC_APPEND(encoded_crlf,encoded_crlf_length); + } + // fhb_log (wb->fh,encoded_crlf); +} + +int write_cc_buffer_as_transcript (struct eia608_screen *data, struct s_write *wb) +{ + int i; + + int wrote_something = 0; + if (debug_608) + { + hb_log ("\n- - - TRANSCRIPT caption - - -\n"); + } + for (i=0;i<15;i++) + { + if (data->row_used[i]) + { + write_cc_line_as_transcript (data,wb, i); + } + wrote_something=1; + } + if (debug_608) + { + hb_log ("- - - - - - - - - - - -\r\n"); + } + return wrote_something; +} + +void write_cc_buffer_to_gui (struct eia608_screen *data, struct s_write *wb) +{ + unsigned h1,m1,s1,ms1; + unsigned h2,m2,s2,ms2; + int i; + + LLONG ms_start= wb->data608->current_visible_start_ms; + + ms_start+=subs_delay; + if (ms_start<0) // Drop screens that because of subs_delay start too early + return; + int time_reported=0; + for (i=0;i<15;i++) + { + if (data->row_used[i]) + { + hb_log ("###SUBTITLE#"); + if (!time_reported) + { + LLONG ms_end = get_fts()+subs_delay; + mstotime (ms_start,&h1,&m1,&s1,&ms1); + mstotime (ms_end-1,&h2,&m2,&s2,&ms2); // -1 To prevent overlapping with next line. + // Note, only MM:SS here as we need to save space in the preview window + hb_log ("%02u:%02u#%02u:%02u#", + h1*60+m1,s1, h2*60+m2,s2); + time_reported=1; + } + else + hb_log ("##"); + + // We don't capitalize here because whatever function that was used + // before to write to file already took care of it. + int length = get_decoder_line_encoded_for_gui (wb->subline, i, data); + fwrite (wb->subline, 1, length, stderr); + fwrite ("\n",1,1,stderr); + } + } + fflush (stderr); +} + +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; + LLONG ms_start= wb->data608->current_visible_start_ms; + int i; + + ms_start+=subs_delay; + if (ms_start<0) // Drop screens that because of subs_delay start too early + return 0; + + LLONG ms_end = get_fts()+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); + enc_buffer_used=encode_line (enc_buffer,(unsigned char *) timeline); + fwrite (enc_buffer,enc_buffer_used,1,wb->fh); + XMLRPC_APPEND(enc_buffer,enc_buffer_used); + sprintf (timeline, "%02u:%02u:%02u,%03u --> %02u:%02u:%02u,%03u\r\n", + h1,m1,s1,ms1, h2,m2,s2,ms2); + enc_buffer_used=encode_line (enc_buffer,(unsigned char *) timeline); + if (debug_608) + { + hb_log ("\n- - - SRT caption - - -\n"); + hb_log (timeline); + } + fwrite (enc_buffer,enc_buffer_used,1,wb->fh); + XMLRPC_APPEND(enc_buffer,enc_buffer_used); + for (i=0;i<15;i++) + { + if (data->row_used[i]) + { + if (sentence_cap) + { + capitalize(i,data, &wb->new_sentence); + correct_case(i,data); + } + int length = get_decoder_line_encoded (wb->subline, i, data); + if (debug_608 && encoding!=ENC_UNICODE) + { + hb_log ("\r"); + hb_log ("%s\n",wb->subline); + } + fwrite (wb->subline, 1, length, wb->fh); + XMLRPC_APPEND(wb->subline,length); + fwrite (encoded_crlf, 1, encoded_crlf_length,wb->fh); + XMLRPC_APPEND(encoded_crlf,encoded_crlf_length); + wrote_something=1; + // fhb_log (wb->fh,encoded_crlf); + } + } + if (debug_608) + { + hb_log ("- - - - - - - - - - - -\r\n"); + } + // fhb_log (wb->fh, encoded_crlf); + fwrite (encoded_crlf, 1, encoded_crlf_length,wb->fh); + XMLRPC_APPEND(encoded_crlf,encoded_crlf_length); + return wrote_something; +} + +int write_cc_buffer_as_sami (struct eia608_screen *data, struct s_write *wb) +{ + int wrote_something=0; + LLONG startms = wb->data608->current_visible_start_ms; + int i; + + startms+=subs_delay; + if (startms<0) // Drop screens that because of subs_delay start too early + return 0; + + LLONG endms = get_fts()+subs_delay; + endms--; // To prevent overlapping with next line. + sprintf ((char *) str,"

\r\n",startms); + if (debug_608 && encoding!=ENC_UNICODE) + { + hb_log ("\r%s\n", str); + } + enc_buffer_used=encode_line (enc_buffer,(unsigned char *) str); + fwrite (enc_buffer,enc_buffer_used,1,wb->fh); + XMLRPC_APPEND(enc_buffer,enc_buffer_used); + for (i=0;i<15;i++) + { + if (data->row_used[i]) + { + int length = get_decoder_line_encoded (wb->subline, i, data); + if (debug_608 && encoding!=ENC_UNICODE) + { + hb_log ("\r"); + hb_log ("%s\n",wb->subline); + } + fwrite (wb->subline, 1, length, wb->fh); + XMLRPC_APPEND(wb->subline,length); + wrote_something=1; + if (i!=14) + { + fwrite (encoded_br, 1, encoded_br_length,wb->fh); + XMLRPC_APPEND(encoded_br, encoded_br_length); + } + fwrite (encoded_crlf, 1, encoded_crlf_length,wb->fh); + XMLRPC_APPEND(encoded_crlf, encoded_crlf_length); + } + } + sprintf ((char *) str,"

\r\n"); + if (debug_608 && encoding!=ENC_UNICODE) + { + hb_log ("\r%s\n", str); + } + enc_buffer_used=encode_line (enc_buffer,(unsigned char *) str); + fwrite (enc_buffer,enc_buffer_used,1,wb->fh); + XMLRPC_APPEND(enc_buffer,enc_buffer_used); + sprintf ((char *) str,"

 

\r\n\r\n",endms); + if (debug_608 && encoding!=ENC_UNICODE) + { + hb_log ("\r%s\n", str); + } + enc_buffer_used=encode_line (enc_buffer,(unsigned char *) str); + fwrite (enc_buffer,enc_buffer_used,1,wb->fh); + XMLRPC_APPEND(enc_buffer,enc_buffer_used); + return wrote_something; +} + +struct eia608_screen *get_current_visible_buffer (struct s_write *wb) +{ + struct eia608_screen *data; + if (wb->data608->visible_buffer==1) + data = &wb->data608->buffer1; + else + data = &wb->data608->buffer2; + return data; +} + + +int write_cc_buffer (struct s_write *wb) +{ + struct eia608_screen *data; + int wrote_something=0; + if (screens_to_process!=-1 && wb->data608->screenfuls_counter>=screens_to_process) + { + // We are done. + processed_enough=1; + return 0; + } + if (wb->data608->visible_buffer==1) + data = &wb->data608->buffer1; + else + data = &wb->data608->buffer2; + + if (!data->empty) + { + wb->new_sentence=1; + switch (write_format) + { + case OF_SRT: + wrote_something = write_cc_buffer_as_srt (data, wb); + break; + case OF_SAMI: + wrote_something = write_cc_buffer_as_sami (data,wb); + break; + case OF_TRANSCRIPT: + wrote_something = write_cc_buffer_as_transcript (data,wb); + break; + default: + break; + } + if (wrote_something && gui_mode_reports) + write_cc_buffer_to_gui (data,wb); + } + return wrote_something; +} + +void roll_up(struct s_write *wb) +{ + struct eia608_screen *use_buffer; + int i, j; + + if (wb->data608->visible_buffer==1) + use_buffer = &wb->data608->buffer1; + else + use_buffer = &wb->data608->buffer2; + int keep_lines; + switch (wb->data608->mode) + { + case MODE_ROLLUP_2: + keep_lines=2; + break; + case MODE_ROLLUP_3: + keep_lines=3; + break; + case MODE_ROLLUP_4: + keep_lines=4; + break; + default: // Shouldn't happen + keep_lines=0; + break; + } + 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++) + { + if (use_buffer->row_used[i]) + { + rows_now++; + if (firstrow==-1) + firstrow=i; + lastrow=i; + } + } + + if (debug_608) + hb_log ("\rIn roll-up: %d lines used, first: %d, last: %d\n", rows_now, firstrow, lastrow); + + if (lastrow==-1) // Empty screen, nothing to rollup + return; + + for (j=lastrow-keep_lines+1;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]; + } + } + 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[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; + + // Sanity check + rows_now=0; + for (i=0;i<15;i++) + if (use_buffer->row_used[i]) + rows_now++; + if (rows_now>keep_lines) + hb_log ("Bug in roll_up, should have %d lines but I have %d.\n", + keep_lines, rows_now); +} + +void erase_memory (struct s_write *wb, int displayed) +{ + struct eia608_screen *buf; + if (displayed) + { + if (wb->data608->visible_buffer==1) + buf=&wb->data608->buffer1; + else + buf=&wb->data608->buffer2; + } + else + { + if (wb->data608->visible_buffer==1) + buf=&wb->data608->buffer2; + else + buf=&wb->data608->buffer1; + } + clear_eia608_cc_buffer (buf); +} + +int is_current_row_empty (struct s_write *wb) +{ + struct eia608_screen *use_buffer; + int i; + + if (wb->data608->visible_buffer==1) + use_buffer = &wb->data608->buffer1; + else + use_buffer = &wb->data608->buffer2; + for (i=0;icharacters[wb->data608->rollup_base_row][i]!=' ') + return 0; + } + return 1; +} + +/* Process GLOBAL CODES */ +void handle_command (/*const */ unsigned char c1, const unsigned char c2, struct s_write *wb) +{ + // Handle channel change + wb->data608->channel=wb->new_channel; + if (wb->data608->channel!=cc_channel) + return; + + enum command_code command = COM_UNKNOWN; + if (c1==0x15) + c1=0x14; + if ((c1==0x14 || c1==0x1C) && c2==0x2C) + command = COM_ERASEDISPLAYEDMEMORY; + if ((c1==0x14 || c1==0x1C) && c2==0x20) + command = COM_RESUMECAPTIONLOADING; + if ((c1==0x14 || c1==0x1C) && c2==0x2F) + command = COM_ENDOFCAPTION; + if ((c1==0x17 || c1==0x1F) && c2==0x21) + command = COM_TABOFFSET1; + if ((c1==0x17 || c1==0x1F) && c2==0x22) + command = COM_TABOFFSET2; + if ((c1==0x17 || c1==0x1F) && c2==0x23) + command = COM_TABOFFSET3; + if ((c1==0x14 || c1==0x1C) && c2==0x25) + command = COM_ROLLUP2; + if ((c1==0x14 || c1==0x1C) && c2==0x26) + command = COM_ROLLUP3; + if ((c1==0x14 || c1==0x1C) && c2==0x27) + command = COM_ROLLUP4; + if ((c1==0x14 || c1==0x1C) && c2==0x2D) + command = COM_CARRIAGERETURN; + if ((c1==0x14 || c1==0x1C) && c2==0x2E) + command = COM_ERASENONDISPLAYEDMEMORY; + if ((c1==0x14 || c1==0x1C) && c2==0x21) + command = COM_BACKSPACE; + if ((c1==0x14 || c1==0x1C) && c2==0x2b) + command = COM_RESUMETEXTDISPLAY; + if (debug_608) + { + hb_log ("\rCommand: %02X %02X (%s)\n",c1,c2,command_type[command]); + } + switch (command) + { + case COM_BACKSPACE: + if (wb->data608->cursor_column>0) + { + wb->data608->cursor_column--; + get_writing_buffer(wb)->characters[wb->data608->cursor_row][wb->data608->cursor_column]=' '; + } + break; + case COM_TABOFFSET1: + if (wb->data608->cursor_column<31) + wb->data608->cursor_column++; + break; + case COM_TABOFFSET2: + wb->data608->cursor_column+=2; + if (wb->data608->cursor_column>31) + wb->data608->cursor_column=31; + break; + case COM_TABOFFSET3: + wb->data608->cursor_column+=3; + if (wb->data608->cursor_column>31) + wb->data608->cursor_column=31; + break; + case COM_RESUMECAPTIONLOADING: + wb->data608->mode=MODE_POPUP; + break; + case COM_RESUMETEXTDISPLAY: + wb->data608->mode=MODE_TEXT; + break; + case COM_ROLLUP2: + if (wb->data608->mode==MODE_POPUP) + { + if (write_cc_buffer (wb)) + wb->data608->screenfuls_counter++; + erase_memory (wb, 1); + } + if (wb->data608->mode==MODE_ROLLUP_2 && !is_current_row_empty(wb)) + { + if (debug_608) + hb_log ("Two RU2, current line not empty. Simulating a CR\n"); + handle_command(0x14, 0x2D, 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; + break; + case COM_ROLLUP3: + if (wb->data608->mode==MODE_POPUP) + { + if (write_cc_buffer (wb)) + wb->data608->screenfuls_counter++; + erase_memory (wb, 1); + } + if (wb->data608->mode==MODE_ROLLUP_3 && !is_current_row_empty(wb)) + { + if (debug_608) + hb_log ("Two RU3, current line not empty. Simulating a CR\n"); + handle_command(0x14, 0x2D, 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; + break; + case COM_ROLLUP4: + if (wb->data608->mode==MODE_POPUP) + { + if (write_cc_buffer (wb)) + wb->data608->screenfuls_counter++; + erase_memory (wb, 1); + } + if (wb->data608->mode==MODE_ROLLUP_4 && !is_current_row_empty(wb)) + { + if (debug_608) + hb_log ("Two RU4, current line not empty. Simulating a CR\n"); + handle_command(0x14, 0x2D, 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: + // In transcript mode, CR doesn't write the whole screen, to avoid + // repeated lines. + if (write_format==OF_TRANSCRIPT) + { + write_cc_line_as_transcript(get_current_visible_buffer (wb), wb, wb->data608->cursor_row); + } + else + { + 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->current_visible_start_ms=get_fts(); + wb->data608->cursor_column=0; + break; + case COM_ERASENONDISPLAYEDMEMORY: + erase_memory (wb,0); + break; + case COM_ERASEDISPLAYEDMEMORY: + // Write it to disk before doing this, and make a note of the new + // time it became clear. + if (write_format==OF_TRANSCRIPT && + (wb->data608->mode==MODE_ROLLUP_2 || wb->data608->mode==MODE_ROLLUP_3 || + wb->data608->mode==MODE_ROLLUP_4)) + { + // In transcript mode we just write the cursor line. The previous lines + // should have been written already, so writing everything produces + // duplicate lines. + write_cc_line_as_transcript(get_current_visible_buffer (wb), wb, wb->data608->cursor_row); + } + else + { + if (write_cc_buffer (wb)) + wb->data608->screenfuls_counter++; + } + erase_memory (wb,1); + wb->data608->current_visible_start_ms=get_fts(); + break; + case COM_ENDOFCAPTION: // Switch buffers + // The currently *visible* buffer is leaving, so now we know it's ending + // time. Time to actually write it to file. + if (write_cc_buffer (wb)) + wb->data608->screenfuls_counter++; + wb->data608->visible_buffer = (wb->data608->visible_buffer==1) ? 2 : 1; + wb->data608->current_visible_start_ms=get_fts(); + wb->data608->cursor_column=0; + wb->data608->cursor_row=0; + wb->data608->color=default_color; + wb->data608->font=FONT_REGULAR; + break; + default: + if (debug_608) + { + hb_log ("\rNot yet implemented.\n"); + } + break; + } +} + +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) +{ + unsigned char c; + if (wb->data608->channel!=cc_channel) + return; + if (c2>=0x30 && c2<=0x3f) + { + c=c2 + 0x50; // So if c>=0x80 && c<=0x8f, it comes from here + if (debug_608) + hb_log ("\rDouble: %02X %02X --> %c\n",c1,c2,c); + write_char(c,wb); + } +} + +/* Process EXTENDED CHARACTERS */ +unsigned char handle_extended (unsigned char hi, unsigned char lo, struct s_write *wb) +{ + // Handle channel change + if (wb->new_channel > 2) + { + wb->new_channel -= 2; + if (debug_608) + hb_log ("\nChannel correction, now %d\n", wb->new_channel); + } + wb->data608->channel=wb->new_channel; + if (wb->data608->channel!=cc_channel) + return 0; + + // For lo values between 0x20-0x3f + unsigned char c=0; + + if (debug_608) + hb_log ("\rExtended: %02X %02X\n",hi,lo); + if (lo>=0x20 && lo<=0x3f && (hi==0x12 || hi==0x13)) + { + switch (hi) + { + case 0x12: + c=lo+0x70; // So if c>=0x90 && c<=0xaf it comes from here + break; + case 0x13: + c=lo+0x90; // So if c>=0xb0 && c<=0xcf it comes from here + break; + } + // This column change is because extended characters replace + // the previous character (which is sent for basic decoders + // to show something similar to the real char) + if (wb->data608->cursor_column>0) + wb->data608->cursor_column--; + + write_char (c,wb); + } + return 1; +} + +/* Process PREAMBLE ACCESS CODES (PAC) */ +void handle_pac (unsigned char c1, unsigned char c2, struct s_write *wb) +{ + // Handle channel change + if (wb->new_channel > 2) + { + wb->new_channel -= 2; + if (debug_608) + hb_log ("\nChannel correction, now %d\n", wb->new_channel); + } + wb->data608->channel=wb->new_channel; + if (wb->data608->channel!=cc_channel) + return; + + int row=rowdata[((c1<<1)&14)|((c2>>5)&1)]; + + if (debug_608) + hb_log ("\rPAC: %02X %02X",c1,c2); + + if (c2>=0x40 && c2<=0x5f) + { + c2=c2-0x40; + } + else + { + if (c2>=0x60 && c2<=0x7f) + { + c2=c2-0x60; + } + else + { + if (debug_608) + hb_log ("\rThis is not a PAC!!!!!\n"); + return; + } + } + int color=pac2_attribs[c2][0]; + int font=pac2_attribs[c2][1]; + 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) + { + // 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->rollup_base_row=row-1; + wb->data608->cursor_column=indent; +} + + +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 + if (debug_608) + hb_log ("%c",c1); + + /*if (debug_608) + hb_log ("Character: %02X (%c) -> %02X (%c)\n",c1,c1,c,c); */ + write_char (c1,wb); +} + +int check_channel (unsigned char c1, struct s_write *wb) +{ + if (c1==0x14) + { + if (debug_608 && wb->data608->channel!=1) + hb_log ("\nChannel change, now 1\n"); + return 1; + } + if (c1==0x1c) + { + if (debug_608 && wb->data608->channel!=2) + hb_log ("\nChannel change, now 2\n"); + return 2; + } + if (c1==0x15) + { + if (debug_608 && wb->data608->channel!=3) + hb_log ("\nChannel change, now 3\n"); + return 3; + } + if (c1==0x1d) + { + if (debug_608 && wb->data608->channel!=4) + hb_log ("\nChannel change, now 4\n"); + return 4; + } + + // Otherwise keep the current channel + return wb->data608->channel; +} + +/* 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) +{ + int wrote_to_screen=0; + + /* Full channel changes are only allowed for "GLOBAL CODES", + * "OTHER POSITIONING CODES", "BACKGROUND COLOR CODES", + * "MID-ROW CODES". + * "PREAMBLE ACCESS CODES", "BACKGROUND COLOR CODES" and + * SPECIAL/SPECIAL CHARACTERS allow only switching + * between 1&3 or 2&4. */ + wb->new_channel = check_channel (hi,wb); + //if (wb->data608->channel!=cc_channel) + // continue; + + if (hi>=0x18 && hi<=0x1f) + hi=hi-8; + + switch (hi) + { + case 0x10: + if (lo>=0x40 && lo<=0x5f) + handle_pac (hi,lo,wb); + break; + case 0x11: + if (lo>=0x20 && lo<=0x2f) + handle_text_attr (hi,lo,wb); + if (lo>=0x30 && lo<=0x3f) + { + wrote_to_screen=1; + handle_double (hi,lo,wb); + } + if (lo>=0x40 && lo<=0x7f) + handle_pac (hi,lo,wb); + break; + case 0x12: + case 0x13: + if (lo>=0x20 && lo<=0x3f) + { + wrote_to_screen=handle_extended (hi,lo,wb); + } + if (lo>=0x40 && lo<=0x7f) + handle_pac (hi,lo,wb); + break; + case 0x14: + case 0x15: + if (lo>=0x20 && lo<=0x2f) + handle_command (hi,lo,wb); + if (lo>=0x40 && lo<=0x7f) + handle_pac (hi,lo,wb); + break; + case 0x16: + if (lo>=0x40 && lo<=0x7f) + handle_pac (hi,lo,wb); + break; + case 0x17: + if (lo>=0x21 && lo<=0x22) + handle_command (hi,lo,wb); + if (lo>=0x2e && lo<=0x2f) + handle_text_attr (hi,lo,wb); + if (lo>=0x40 && lo<=0x7f) + handle_pac (hi,lo,wb); + break; + } + return wrote_to_screen; +} + +void process608 (const unsigned char *data, int length, struct s_write *wb) +{ + static int textprinted = 0; + int i; + + if (data!=NULL) + { + for (i=0;i=0x01 && hi<=0x0E) + { + // XDS crap - mode. Would be nice to support it eventually + // wb->data608->last_c1=0; + // wb->data608->last_c2=0; + wb->data608->channel=3; // Always channel 3 + wb->in_xds_mode=1; + } + if (hi==0x0F) // End of XDS block + { + wb->in_xds_mode=0; + continue; + } + if (hi>=0x10 && hi<0x1F) // Non-character code or special/extended char + // http://www.geocities.com/mcpoodle43/SCC_TOOLS/DOCS/CC_CODES.HTML + // http://www.geocities.com/mcpoodle43/SCC_TOOLS/DOCS/CC_CHARS.HTML + { + // We were writing characters before, start a new line for + // diagnostic output from disCommand() + if (debug_608 && textprinted == 1 ) + { + hb_log("\n"); + textprinted = 0; + } + + wb->in_xds_mode=0; // Back to normal + if (wb->data608->last_c1==hi && wb->data608->last_c2==lo) + { + // Duplicate dual code, discard + continue; + } + wb->data608->last_c1=hi; + wb->data608->last_c2=lo; + wrote_to_screen=disCommand (hi,lo,wb); + } + if (hi>=0x20) // Standard characters (always in pairs) + { + // Only print if the channel is active + if (wb->data608->channel!=cc_channel) + continue; + + if (debug_608) + { + if( textprinted == 0 ) + { + hb_log("\n"); + textprinted = 1; + } + } + + handle_single(hi,wb); + handle_single(lo,wb); + wrote_to_screen=1; + wb->data608->last_c1=0; + wb->data608->last_c2=0; + } + + 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())); + } + + if (wrote_to_screen && direct_rollup && // If direct_rollup is enabled and + (wb->data608->mode==MODE_ROLLUP_2 || // we are in rollup mode, write now. + wb->data608->mode==MODE_ROLLUP_3 || + wb->data608->mode==MODE_ROLLUP_4)) + { + // We don't increase screenfuls_counter here. + write_cc_buffer (wb); + wb->data608->current_visible_start_ms=get_fts(); + } + } + } +} + + +/* 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; +} diff --git a/libhb/deccc608sub.h b/libhb/deccc608sub.h new file mode 100644 index 00000000..e6c1518a --- /dev/null +++ b/libhb/deccc608sub.h @@ -0,0 +1,131 @@ +/* + * 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. + */ +#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 +{ + MODE_POPUP = 0, + MODE_ROLLUP_2 = 1, + MODE_ROLLUP_3 = 2, + MODE_ROLLUP_4 = 3, + MODE_TEXT = 4 +}; + +enum color_code +{ + COL_WHITE = 0, + COL_GREEN = 1, + COL_BLUE = 2, + COL_CYAN = 3, + COL_RED = 4, + COL_YELLOW = 5, + COL_MAGENTA = 6, + COL_USERDEFINED = 7 +}; + + +enum font_bits +{ + FONT_REGULAR = 0, + FONT_ITALICS = 1, + FONT_UNDERLINED = 2, + FONT_UNDERLINED_ITALICS = 3 +}; + + +struct eia608_screen // A CC buffer +{ + 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? +}; + +#define LLONG long long + +struct eia608 +{ + struct eia608_screen buffer1; + struct eia608_screen buffer2; + int cursor_row, cursor_column; + int visible_buffer; + int srt_counter; // Number of subs currently written + int screenfuls_counter; // Number of meaningful screenfuls written + LLONG 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 + unsigned char color; // Color we are currently using to write + unsigned char font; // Font we are currently using to write + int rollup_base_row; +}; + +struct s_write { + struct eia608 *data608; + FILE *fh; + unsigned char *subline; + int new_sentence; + int new_channel; + int in_xds_mode; + hb_subtitle_t * subtitle; + uint64_t last_pts; +}; + +enum command_code +{ + COM_UNKNOWN = 0, + COM_ERASEDISPLAYEDMEMORY = 1, + COM_RESUMECAPTIONLOADING = 2, + COM_ENDOFCAPTION = 3, + COM_TABOFFSET1 = 4, + COM_TABOFFSET2 = 5, + COM_TABOFFSET3 = 6, + COM_ROLLUP2 = 7, + COM_ROLLUP3 = 8, + COM_ROLLUP4 = 9, + COM_CARRIAGERETURN = 10, + COM_ERASENONDISPLAYEDMEMORY = 11, + COM_BACKSPACE = 12, + COM_RESUMETEXTDISPLAY = 13 +}; + +enum encoding_type +{ + ENC_UNICODE = 0, + ENC_LATIN_1 = 1, + ENC_UTF_8 = 2 +}; + +enum output_format +{ + OF_RAW = 0, + OF_SRT = 1, + OF_SAMI = 2, + OF_TRANSCRIPT = 3, + OF_RCWT = 4 +}; + +#endif diff --git a/libhb/decmpeg2.c b/libhb/decmpeg2.c index 6ad6127d..9d28c977 100644 --- a/libhb/decmpeg2.c +++ b/libhb/decmpeg2.c @@ -7,6 +7,7 @@ #include "hb.h" #include "hbffmpeg.h" #include "mpeg2dec/mpeg2.h" +#include "deccc608sub.h" /* Cadence tracking */ #ifndef PIC_FLAG_REPEAT_FIRST_FIELD @@ -34,6 +35,7 @@ typedef struct hb_libmpeg2_s mpeg2dec_t * libmpeg2; const mpeg2_info_t * info; hb_job_t * job; + hb_title_t * title; int width; int height; int rate; @@ -45,6 +47,8 @@ typedef struct hb_libmpeg2_s int64_t last_pts; int cadence[12]; int flag; + struct s_write cc608; /* Closed Captions */ + hb_subtitle_t * subtitle; } hb_libmpeg2_t; /********************************************************************** @@ -60,9 +64,60 @@ static hb_libmpeg2_t * hb_libmpeg2_init() m->info = mpeg2_info( m->libmpeg2 ); m->last_pts = -1; + /* Closed Captions init, whether needed or not */ + general_608_init( &m->cc608 ); + m->cc608.data608 = calloc(1, sizeof(struct eia608)); return m; } +static void hb_mpeg2_cc( hb_libmpeg2_t * m, uint8_t *cc_block ) +{ + uint8_t cc_valid = (*cc_block & 4) >>2; + uint8_t cc_type = *cc_block & 3; + + if( !m->job ) + { + /* + * Ignore CC decoding during scanning. + */ + return; + } + + if (cc_valid || cc_type==3) + { + switch (cc_type) + { + case 0: + // CC1 stream + process608( cc_block+1, 2, &m->cc608 ); + break; + case 1: + // CC2 stream + //process608( cc_block+1, 2, &m->cc608 ); + break; + case 2: //EIA-708 + // DTVCC packet data + // Fall through + case 3: //EIA-708 + { + uint8_t temp[4]; + temp[0]=cc_valid; + temp[1]=cc_type; + temp[2]=cc_block[1]; + temp[3]=cc_block[2]; + //do_708 ((const unsigned char *) temp, 4); + } + break; + default: + break; + } + } + else + { + hb_log("Ignoring invalid CC block"); + } +} + static hb_buffer_t *hb_copy_frame( hb_job_t *job, int width, int height, uint8_t* y, uint8_t *u, uint8_t *v ) { @@ -128,7 +183,7 @@ static hb_buffer_t *hb_copy_frame( hb_job_t *job, int width, int height, * *********************************************************************/ static int hb_libmpeg2_decode( hb_libmpeg2_t * m, hb_buffer_t * buf_es, - hb_list_t * list_raw ) + hb_list_t * list_raw ) { mpeg2_state_t state; hb_buffer_t * buf; @@ -345,6 +400,116 @@ static int hb_libmpeg2_decode( hb_libmpeg2_t * m, hb_buffer_t * buf_es, { mpeg2_reset( m->libmpeg2, 0 ); } + + /* + * Look for Closed Captions if scanning (!job) or if closed captions have been requested. + */ + if( ( !m->job || m->subtitle) && + ( m->info->user_data_len != 0 && + m->info->user_data[0] == 0x43 && + m->info->user_data[1] == 0x43 ) ) + { + int i, j; + const uint8_t *header = &m->info->user_data[4]; + uint8_t pattern=header[0] & 0x80; + int field1packet = 0; /* expect Field 1 first */ + if (pattern==0x00) + field1packet=1; /* expect Field 1 second */ + int capcount=(header[0] & 0x1e) / 2; + header++; + + m->cc608.last_pts = m->last_pts; + + /* + * Add closed captions to the title if we are scanning (no job). + * + * Just because we don't add this doesn't mean that there aren't any when + * we scan, just that noone said anything. So you should be able to add + * closed captions some other way (See decmpeg2Init() for alternative + * approach of assuming that there are always CC, which is probably + * safer - however I want to leave the autodetect in here for now to + * see how it goes). + */ + if( !m->job && m->title ) + { + hb_subtitle_t * subtitle; + int found = 0; + int i; + + for( i = 0; i < hb_list_count( m->title->list_subtitle ); i++ ) + { + subtitle = hb_list_item( m->title->list_subtitle, i); + if( subtitle && subtitle->source == CCSUB ) + { + found = 1; + break; + } + } + + if( !found ) + { + subtitle = calloc( sizeof( hb_subtitle_t ), 1 ); + subtitle->track = 0; + subtitle->id = 0x0; + snprintf( subtitle->lang, sizeof( subtitle->lang ), "Closed Captions"); + snprintf( subtitle->iso639_2, sizeof( subtitle->iso639_2 ), "und"); + subtitle->format = TEXTSUB; + subtitle->source = CCSUB; + subtitle->dest = PASSTHRUSUB; + subtitle->type = 5; + + hb_list_add( m->title->list_subtitle, subtitle ); + } + } + + for( i=0; ilibmpeg2 ); + free( m->cc608.data608 ); + general_608_close( &m->cc608 ); + free( m ); *_m = NULL; } @@ -392,6 +560,69 @@ static int decmpeg2Init( hb_work_object_t * w, hb_job_t * job ) pv->libmpeg2->job = job; + if( job && job->title ) { + pv->libmpeg2->title = job->title; + } + + /* + * If not scanning, then are we supposed to extract Closed Captions? + */ + if( job ) + { + hb_subtitle_t * subtitle; + int i; + + for( i = 0; i < hb_list_count( job->list_subtitle ); i++ ) + { + subtitle = hb_list_item( job->list_subtitle, i); + if( subtitle && subtitle->source == CCSUB ) + { + pv->libmpeg2->subtitle = subtitle; + pv->libmpeg2->cc608.subtitle = subtitle; + break; + } + } + + } + + /* + * During a scan add a Closed Caption subtitle track to the title, + * since we may have CC. Don't bother actually trying to detect CC + * since we'd have to go through too much of the source. + * + if( !job && w->title ) + { + hb_subtitle_t * subtitle; + int found = 0; + int i; + + for( i = 0; i < hb_list_count( w->title->list_subtitle ); i++ ) + { + subtitle = hb_list_item( w->title->list_subtitle, i); + if( subtitle && subtitle->source == CCSUB ) + { + found = 1; + break; + } + } + + if( !found ) + { + subtitle = calloc( sizeof( hb_subtitle_t ), 1 ); + subtitle->track = 0; + subtitle->id = 0x0; + snprintf( subtitle->lang, sizeof( subtitle->lang ), "Closed Captions"); + snprintf( subtitle->iso639_2, sizeof( subtitle->iso639_2 ), "und"); + subtitle->format = TEXTSUB; + subtitle->source = CCSUB; + subtitle->dest = PASSTHRUSUB; + subtitle->type = 5; + + hb_list_add( w->title->list_subtitle, subtitle ); + } + } + */ + return 0; } @@ -401,12 +632,16 @@ static int decmpeg2Init( hb_work_object_t * w, hb_job_t * job ) * *********************************************************************/ static int decmpeg2Work( hb_work_object_t * w, hb_buffer_t ** buf_in, - hb_buffer_t ** buf_out ) + hb_buffer_t ** buf_out ) { hb_work_private_t * pv = w->private_data; hb_buffer_t * buf, * last = NULL; int status = HB_WORK_OK; + if( w->title && pv && pv->libmpeg2 && !pv->libmpeg2->title ) { + pv->libmpeg2->title = w->title; + } + // The reader found a chapter break, consume it completely, and remove it from the // stream. We need to shift it. if( (*buf_in)->new_chap ) diff --git a/libhb/decsub.c b/libhb/decvobsub.c similarity index 100% rename from libhb/decsub.c rename to libhb/decvobsub.c diff --git a/libhb/muxcommon.c b/libhb/muxcommon.c index b6008dc3..30536dd0 100644 --- a/libhb/muxcommon.c +++ b/libhb/muxcommon.c @@ -223,22 +223,28 @@ static void MuxerFunc( void * _mux ) { switch( job->mux ) { - case HB_MUX_MP4: - case HB_MUX_PSP: - case HB_MUX_IPOD: - m = hb_mux_mp4_init( job ); - break; - case HB_MUX_AVI: - m = hb_mux_avi_init( job ); - break; - case HB_MUX_OGM: - m = hb_mux_ogm_init( job ); - break; - case HB_MUX_MKV: - m = hb_mux_mkv_init( job ); + case HB_MUX_MP4: + case HB_MUX_PSP: + case HB_MUX_IPOD: + m = hb_mux_mp4_init( job ); + break; + case HB_MUX_AVI: + m = hb_mux_avi_init( job ); + break; + case HB_MUX_OGM: + m = hb_mux_ogm_init( job ); + break; + case HB_MUX_MKV: + m = hb_mux_mkv_init( job ); + default: + hb_error( "No muxer selected, exiting" ); + *job->die = 1; } /* Create file, write headers */ - m->init( m ); + if( m ) + { + m->init( m ); + } } /* Build list of fifos we're interested in */ @@ -254,8 +260,8 @@ static void MuxerFunc( void * _mux ) // The following 'while' is the main muxing loop. - int thread_sleep_interval = 50; - while( !*job->die ) + int thread_sleep_interval = 50; + while( !*job->die ) { MoveToInternalFifos( mux ); if ( mux->rdy != mux->allRdy ) @@ -316,10 +322,13 @@ finished: /* Update the UI */ hb_state_t state; state.state = HB_STATE_MUXING; - p.progress = 0; + p.progress = 0; hb_set_state( job->h, &state ); #undef p - m->end( m ); + if( m ) + { + m->end( m ); + } if( !stat( job->file, &sb ) ) { @@ -353,8 +362,11 @@ finished: } } } - - free( m ); + + if( m ) + { + free( m ); + } for( i = 0; i < mux->ntracks; ++i ) { diff --git a/libhb/muxmp4.c b/libhb/muxmp4.c index 29d08ca8..81ee2f8a 100644 --- a/libhb/muxmp4.c +++ b/libhb/muxmp4.c @@ -590,6 +590,7 @@ static int MP4End( hb_mux_object_t * m ) { hb_job_t * job = m->job; hb_title_t * title = job->title; + int i; /* Write our final chapter marker */ if( m->job->chapter_markers ) @@ -666,6 +667,37 @@ static int MP4End( hb_mux_object_t * m ) MP4TagsFree( tags ); } + /* + * Display any text subs. + * + * This is placeholder code, what needs to happen is that we need to + * convert these PTS (which are pre-sync ones) into HB timestamps, + * I guess sync should do that? + * + * And then this needs to move into the Mux code above and insert + * subtitle samples into the MP4 at the correct times. + */ + for( i = 0; i < hb_list_count( job->list_subtitle ); i++ ) + { + hb_subtitle_t *subtitle = hb_list_item( job->list_subtitle, i ); + + if( subtitle && subtitle->format == TEXTSUB && + subtitle->dest == PASSTHRUSUB ) + { + /* + * Should be adding this one if the timestamp is right. + */ + hb_buffer_t *buffer; + + while( (buffer = hb_fifo_get( subtitle->fifo_raw )) != NULL ) + { + hb_log("MuxMP4: Text Sub: %s", buffer->data); + + hb_buffer_close( &buffer ); + } + } + } + MP4Close( m->file ); if ( job->mp4_optimize ) diff --git a/libhb/scan.c b/libhb/scan.c index 420904b6..6ff83a87 100644 --- a/libhb/scan.c +++ b/libhb/scan.c @@ -440,6 +440,7 @@ static int DecodePreviews( hb_scan_t * data, hb_title_t * title ) int vcodec = title->video_codec? title->video_codec : WORK_DECMPEG2; hb_work_object_t *vid_decoder = hb_get_work( vcodec ); vid_decoder->codec_param = title->video_codec_param; + vid_decoder->title = title; vid_decoder->init( vid_decoder, NULL ); hb_buffer_t * vid_buf = NULL; int vidskip = 0; diff --git a/libhb/work.c b/libhb/work.c index 8c6db7d5..b413c78f 100644 --- a/libhb/work.c +++ b/libhb/work.c @@ -272,13 +272,16 @@ void hb_display_job_info( hb_job_t * job ) } } - for( i=0; i < hb_list_count(title->list_subtitle); i++ ) + for( i=0; i < hb_list_count( title->list_subtitle ); i++ ) { subtitle = hb_list_item( title->list_subtitle, i ); if( subtitle ) { - hb_log( " * subtitle track %i, %s (id %x)", subtitle->track, subtitle->lang, subtitle->id); + hb_log( " * subtitle track %i, %s (id %x) %s [%s] -> %s ", subtitle->track, subtitle->lang, subtitle->id, + subtitle->format == PICTURESUB ? "Picture" : "Text", + subtitle->source == VOBSUB ? "VOBSUB" : (subtitle->source == CCSUB ? "CC" : "SRT"), + subtitle->dest == RENDERSUB ? "Render/Burn in" : "Pass-Through"); } } diff --git a/test/test.c b/test/test.c index 411c20c6..a1d9262d 100644 --- a/test/test.c +++ b/test/test.c @@ -1645,9 +1645,11 @@ static int HandleEvents( hb_handle_t * h ) * Find the subtitle with the same track as "sub" and * add that to the job subtitle list */ - subtitle = hb_list_item( title->list_subtitle, sub ); + subtitle = hb_list_item( title->list_subtitle, sub-1 ); if( subtitle ) { hb_list_add( job->list_subtitle, subtitle ); + } else { + fprintf( stderr, "Could not find subtitle track %d, skipped\n", sub ); } } -- 2.11.0