/*
 * 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 = 1;
static int nofontcolor = 0;
static enum encoding_type encoding = ENC_UTF_8;
static int cc_channel = 1;
static enum output_format write_format = OF_SRT;
static int sentence_cap = 0;
static int subs_delay = 0;
static int64_t screens_to_process = -1;
static int processed_enough = 0;
static int gui_mode_reports = 0;
static int norollup = 1;
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)
{
    return wb->last_pts;
}

#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 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
{
    {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 <!-- comments
"<SAMI>\n\
<HEAD>\n\
<STYLE TYPE=\"text/css\">\n\
<!--\n\
P {margin-left: 16pt; margin-right: 16pt; margin-bottom: 16pt; margin-top: 16pt;\n\
text-align: center; font-size: 18pt; font-family: arial; font-weight: bold; color: #f0f0f0;}\n\
.UNKNOWNCC {Name:Unknown; lang:en-US; SAMIType:CC;}\n\
-->\n\
</STYLE>\n\
</HEAD>\n\n\
<BODY>\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","<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=\""}
};

int general_608_init (struct s_write *wb)
{
    if( !wb->enc_buffer )
    {
        wb->enc_buffer=(unsigned char *) malloc (INITIAL_ENC_BUFFER_CAPACITY); 
        if (wb->enc_buffer==NULL)
            return -1;
        wb->enc_buffer_capacity=INITIAL_ENC_BUFFER_CAPACITY;
    }

    if( !wb->subline) {
        wb->subline = malloc(2048);
    
        if (!wb->subline)
        {
            return -1;
        }
    }

    wb->new_sentence = 1;
    wb->new_channel = 1;
    wb->in_xds_mode = 0;

    wb->hb_buffer = NULL;
    wb->hb_last_buffer = NULL;
    wb->last_pts = 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( wb->enc_buffer ) {
        free(wb->enc_buffer);
        wb->enc_buffer_capacity = 0;
        wb->enc_buffer_used = 0;
    }
    if( wb->subline ) {
        free(wb->subline);
    }

    if( wb->hb_buffer ) {
        hb_buffer_close( &wb->hb_buffer );
    }
}


#include <ctype.h>

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==0x00)
        return 0;
    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=0xc2;
            *(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 (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)
{
    int i;

    for (i=0;i<CC608_SCREEN_WIDTH;i++)
    {
        switch (data->characters[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;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);

    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 *) "</font>");
            }
            // 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 
                // <font> 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 *) "<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;
        }
        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>");
    }
    *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 (int64_t milli, unsigned *hours, unsigned *minutes,
               unsigned *seconds, unsigned *ms)
{
    // 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 write_subtitle_file_footer (struct s_write *wb)
{
    switch (write_format)
    {
        case OF_SAMI:
            sprintf ((char *) str,"</BODY></SAMI>\n");
            if (debug_608 && encoding!=ENC_UNICODE)
            {
                hb_log ("\r%s\n", str);
            }
            wb->enc_buffer_used=encode_line (wb->enc_buffer,(unsigned char *) str);
            //fwrite (enc_buffer,enc_buffer_used,1,wb->fh);
            XMLRPC_APPEND(wb->enc_buffer,wb->enc_buffer_used);
            break;
        default: // Nothing to do. Only SAMI has a footer
            break;
    }
}

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

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(wb, strlen (sami_header)*3);
            wb->enc_buffer_used=encode_line (wb->enc_buffer,(unsigned char *) sami_header);
            //fwrite (enc_buffer,enc_buffer_used,1,wb->fh);
            XMLRPC_APPEND(wb->enc_buffer,wb->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( length + 1 );
        buffer->s.frametype = HB_FRAME_SUBTITLE;
        buffer->s.start = wb->data608->current_visible_start_ms;
        buffer->s.stop = get_fts(wb);
        memcpy( buffer->data, wb->subline, length + 1 );
        //hb_log("CC %"PRId64": %s", buffer->s.stop, wb->subline);

        if (wb->hb_last_buffer) {
            wb->hb_last_buffer->next = buffer;
        } else {
            wb->hb_buffer = buffer;
        }
        wb->hb_last_buffer = 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;

    int64_t 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)
            {
                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.
                // 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;
    int64_t 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;

    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);
    //wb->enc_buffer_used=encode_line (wb->enc_buffer,(unsigned char *) timeline);
    //fwrite (wb->enc_buffer,wb->enc_buffer_used,1,wb->fh);
    XMLRPC_APPEND(wb->enc_buffer,wb->enc_buffer_used);
    //sprintf (timeline, "%02u:%02u:%02u,%03u --> %02u:%02u:%02u,%03u\r\n",
    //      h1,m1,s1,ms1, h2,m2,s2,ms2);
    //wb->enc_buffer_used=encode_line (wb->enc_buffer,(unsigned char *) timeline);
    if (debug_608)
    {
        hb_log ("\n- - - SRT caption - - -\n");
        hb_log ("%s", timeline);
    }
    //fwrite (enc_buffer,enc_buffer_used,1,wb->fh);		
    XMLRPC_APPEND(wb->enc_buffer,wb->enc_buffer_used);
    
    /*
     * Write all the lines into enc_buffer, and then write that out at the end
     * 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 line = 1;
    for (i=0;i<15;i++)
    {
        if (data->row_used[i])
        {		
            if (sentence_cap)
            {
                capitalize(i,data, &wb->new_sentence);
                correct_case(i,data);
            }
            /*
             * 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);
                line = 2;
            } else {
                if (line == 2) {
                    wb->enc_buffer_used += encode_line (wb->enc_buffer+wb->enc_buffer_used,
                                                        (unsigned char *) "\n");
                    line = 3;
                } else {
                    wb->enc_buffer_used += encode_line (wb->enc_buffer+wb->enc_buffer_used,
                                                        (unsigned char *) " "); 
                }
                wb->enc_buffer_used += get_decoder_line_encoded (wb->enc_buffer+wb->enc_buffer_used, i, data);
            }
        }
    }
    if (wb->enc_buffer_used)
    {
        hb_buffer_t *buffer = hb_buffer_init( wb->enc_buffer_used + 1 );
        buffer->s.frametype = HB_FRAME_SUBTITLE;
        buffer->s.start = ms_start;
        buffer->s.stop = ms_end;
        memcpy( buffer->data, wb->enc_buffer, wb->enc_buffer_used + 1 );
        if (wb->hb_last_buffer) {
            wb->hb_last_buffer->next = buffer;
        } else {
            wb->hb_buffer = buffer;
        }
        wb->hb_last_buffer = buffer;
        
        wrote_something=1;
    }
    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;
    int64_t 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; 

    int64_t endms   = get_fts(wb)+subs_delay;
    endms--; // To prevent overlapping with next line.
    sprintf ((char *) str,"<SYNC start=\"%"PRId64"\"><P class=\"UNKNOWNCC\">\r\n",startms);
    if (debug_608 && encoding!=ENC_UNICODE)
    {
        hb_log ("\r%s\n", str);
    }
    wb->enc_buffer_used=encode_line (wb->enc_buffer,(unsigned char *) str);
    fwrite (wb->enc_buffer,wb->enc_buffer_used,1,wb->fh);		
    XMLRPC_APPEND(wb->enc_buffer,wb->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,"</P></SYNC>\r\n");
    if (debug_608 && encoding!=ENC_UNICODE)
    {
        hb_log ("\r%s\n", str);
    }
    wb->enc_buffer_used=encode_line (wb->enc_buffer,(unsigned char *) str);
    fwrite (wb->enc_buffer,wb->enc_buffer_used,1,wb->fh);
    XMLRPC_APPEND(wb->enc_buffer,wb->enc_buffer_used);
    sprintf ((char *) str,"<SYNC start=\"%"PRId64"\"><P class=\"UNKNOWNCC\">&nbsp;</P></SYNC>\r\n\r\n",endms);
    if (debug_608 && encoding!=ENC_UNICODE)
    {
        hb_log ("\r%s\n", str);
    }
    wb->enc_buffer_used=encode_line (wb->enc_buffer,(unsigned char *) str);
    fwrite (wb->enc_buffer,wb->enc_buffer_used,1,wb->fh);
    XMLRPC_APPEND(wb->enc_buffer,wb->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<lastrow; j++)
    {
        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];
        }
    }
    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;i<CC608_SCREEN_WIDTH;i++)
    {
        if (use_buffer->characters[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);
            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(wb);
            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);
            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<length;i=i+2)
        {
            unsigned char hi, lo;
            int wrote_to_screen=0; 
            hi = data[i] & 0x7F; // Get rid of parity bit
            lo = data[i+1] & 0x7F; // Get rid of parity bit

            if (hi==0 && lo==0) // Just padding
                continue;
            // hb_log ("\r[%02X:%02X]\n",hi,lo);

            if (hi>=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(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 )
{
    int retval = 1;
    hb_work_private_t * pv;

    pv = calloc( 1, sizeof( hb_work_private_t ) );
    if( pv )
    {
        w->private_data = pv;

        pv->job = job;

        pv->cc608 = calloc(1, sizeof(struct s_write));

        if( pv->cc608 )
        {
            retval = general_608_init(pv->cc608);
            if( !retval )
            {
                pv->cc608->data608 = calloc(1, sizeof(struct eia608));
                if( !pv->cc608->data608 )
                {
                    retval = 1;
                }
            }
        }
    }
    return retval;
}

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;
    hb_buffer_t * in = *buf_in;

    if ( in->size <= 0 )
    {
        /* EOF on input stream - send it downstream & say that we're done */
        handle_end_of_data(pv->cc608);
        /*
         * Grab any pending buffer and output them with the EOF on the end
         */
        if (pv->cc608->hb_last_buffer) {
            pv->cc608->hb_last_buffer->next = in;
            *buf_out = pv->cc608->hb_buffer;
            *buf_in = NULL;
            pv->cc608->hb_buffer = NULL;
            pv->cc608->hb_last_buffer = NULL;
        } else {
            *buf_out = in;
            *buf_in = NULL;
        }
        return HB_WORK_DONE;
    }

    pv->cc608->last_pts = in->s.start;

    process608(in->data, in->size, pv->cc608);

    /*
     * If there is one waiting then pass it on
     */
    *buf_out = pv->cc608->hb_buffer;
    pv->cc608->hb_buffer = NULL;
    pv->cc608->hb_last_buffer = NULL;

    return HB_WORK_OK; 
}

void decccClose( hb_work_object_t * w )
{
    hb_work_private_t * pv = w->private_data;
    general_608_close( pv->cc608 );
    free( pv->cc608->data608 );
    free( pv->cc608 );
    free( w->private_data );
}

hb_work_object_t hb_deccc608 =
{
    WORK_DECCC608,
    "Closed Caption (608) decoder",
    decccInit,
    decccWork,
    decccClose
};