Re: [PATCH] Better subtitling control (v1.1.0)

"Allan N. Snider" <[email protected]> Tue, 03 Feb 2009 15:42:31 -0500
Newsgroups gmane.comp.video.transcode.devel
Message-ID <[email protected]>
    With the patch this time...


Allan N. Snider wrote:
> This patch adds the nextsub filter, (a replacement for extsub).  It
> allows better control over which subtitle indexes can be used, better
> positioning control, allows opacity, zooming, rgb colours, etc.  It's
> well documented (something extsub was in need of).  This is an old
> patch but I see it still didn't make it in, so I'm resubmitting it.
>
>     YES yes, I know.  I need to determine these subtitle indices from
> the VOB header and make them available to dvdrip.  Been on my todo
> list for two years now.  That, and the "forced" flag.  What can I say.
>                                                                      
>                Allan
>
>
filter_nextsub.patch (text/plain, 41.5 KB)
diff -ruN old/filter/extsub/Makefile.am new/filter/extsub/Makefile.am
--- old/filter/extsub/Makefile.am	2007-03-11 22:25:51.000000000 -0400
+++ new/filter/extsub/Makefile.am	2007-03-11 22:24:26.000000000 -0400
@@ -7,12 +7,12 @@
 
 pkgdir = $(MOD_PATH)
 
-pkg_LTLIBRARIES = filter_extsub.la filter_extsub2.la
+pkg_LTLIBRARIES = filter_extsub.la filter_nextsub.la
 
 filter_extsub_la_SOURCES = filter_extsub.c subproc.c subtitle_buffer.c
 filter_extsub_la_LDFLAGS = -module -avoid-version
 
-filter_extsub2_la_SOURCES = filter_extsub.c subproc.c subtitle_buffer.c
-filter_extsub2_la_LDFLAGS = -module -avoid-version
+filter_nextsub_la_SOURCES = filter_nextsub.c subproc.c subtitle_buffer.c
+filter_nextsub_la_LDFLAGS = -module -avoid-version
 
-EXTRA_DIST = subproc.h subtitle_buffer.h
+EXTRA_DIST = subproc.h subtitle_buffer.h nextsub.h
diff -ruN old/filter/extsub/filter_nextsub.c new/filter/extsub/filter_nextsub.c
--- old/filter/extsub/filter_nextsub.c	1969-12-31 19:00:00.000000000 -0500
+++ new/filter/extsub/filter_nextsub.c	2007-03-11 22:43:35.000000000 -0400
@@ -0,0 +1,1272 @@
+/*
+ *  filter_nextsub.c
+ *
+ *  Copyright (C) Thomas Oestreich - January 2002
+ *  Copyright (C) Allan Snider     - March 2007
+ *
+ *  This file is part of transcode, a video stream processing tool
+ *
+ *  transcode is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  transcode is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GNU Make; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+/*
+ * 	v0.3.5 -> v0.4.0:	(Allan Snider)
+ * 		- code style cleanup
+ * 		- add opacity and rendering color for each index
+ * 		- add zoom flag
+ * 		- crop subtitle, (for consistent placement)
+ * 		- modified positioning behaviour
+ * 		- better (?) index guessing
+ */
+
+#define MOD_NAME	"filter_nextsub.so"
+#define MOD_VERSION	"0.4.0 (2007-03-01)"
+#define MOD_CAP		"DVD subtitle overlay plugin"
+#define MOD_AUTHOR	"Thomas Oestreich / Allan Snider"
+
+#include "nextsub.h"
+
+static MyFilterData *mfd;
+
+
+/*
+ *	help_opstr:
+ */
+
+static void
+help_optstr( void )
+{
+    tc_log_info( MOD_NAME, "(%s) help:\n\n"
+	 "* Overview:\n"
+	 "\n"
+	 "      DVD subtitle overlay plugin.\n"
+	 "          Original code by Thomas Destreich,\n"
+	 "          Enhancements added by Allan Snider.\n"
+	 "\n"
+	 "      A subtitle bitmap contains 4 index values, background, text body,\n"
+	 "      inner outline, and outer outline.  The ordering of the indices vary\n"
+	 "      from DVD to DVD.  The background will always be transparent.  One can\n"
+	 "      specify opacity values (0-1) and rendering colors (hex rrggbb) for each\n"
+	 "      index (1-3).  The bitmap can be anti-aliased, and zoomed (XxY).  The\n"
+	 "      resulting bitmap is then cropped giving control of where the subtitle\n"
+	 "      is located, regardless of where the original bitmap had placed it.\n"
+	 "\n"
+	 "      Occasionally, a subtitle component (eg. text body), will be composed\n"
+	 "      of more than one index.  One could assign them the same opacity and\n"
+	 "      color, however, each index is anti-aliased and zoomed individually\n"
+	 "      which would produce poor results.  Therefore, the 'map' option was\n"
+	 "      added, which allows one index value to be remapped to another.  This\n"
+	 "      provides a mechanism to join two index values into one component.\n"
+	 "\n"
+	 "      If no opacity arguments are given, the highest frequency (non-background)\n"
+	 "      subtitle index is assumed to be the body of the text, and the next\n"
+	 "      highest, the inner text outline.  Both indices are assigned an opacity\n"
+	 "      of 1.0.  The text body is assigned the default color and the outline is\n"
+	 "      assigned as black.  The index frequencies are calculated from the first\n"
+	 "      subtitle rendered.\n"
+	 "\n"
+	 "      For positioning, the default (pos=0,topalign=0) places the bottom line of\n"
+	 "      the subtitle at the bottom line of the video frame.  Positive values move\n"
+	 "      the subtitle up by the given number of rows.  If topalign=1, then the\n"
+	 "      top line of the subtitle is placed at the position given (and should be\n"
+	 "      at least the height of the subtitle, else it will be clipped).\n"
+	 "\n"
+	 "      For boolean arguments, a value of TRUE is assumed if the option is\n"
+	 "      present but no value is given.  For opacity arguments, a value\n"
+	 "      of 1.0 is assumed if the option is given with no value.\n"
+	 "\n"
+	 "* Options:\n"
+	 "\n"
+	 "        'track'   Subtitle track (0-255) [0]\n"
+	 "        'forced'  Render only forced subtitles (0=off, 1=on) [0]\n"
+	 "        'pos'     Vertical position of subtitle (0-height) [0]\n"
+	 "        'tshift'  Start time correction (msec) [0]\n"
+	 "        'pre'     Run as a pre filter (0=no, 1=yes) [0]\n"
+	 "        'aalias'  Anti-aliasing (0=off, 1=on) [0]\n"
+	 "        'zoom'    Zoom subtitle image (X[xY]) [1x1]\n"
+	 "        'o1'      Index 1 opacity (0-1) [0]\n"
+	 "        'o2'      Index 2 opacity (0-1) [0]\n"
+	 "        'o3'      Index 3 opacity (0-1) [0]\n"
+	 "        'c1'      Index 1 rendering color (rr[ggbb]) [clr]\n"
+	 "        'c2'      Index 2 rendering color (rr[ggbb]) [clr]\n"
+	 "        'c3'      Index 3 rendering color (rr[ggbb]) [clr]\n"
+	 "        'clr'     Default rendering color (rr[ggbb]) [ffffff]\n"
+	 "        'map'     Remap an index value (ItoJ) [0to0]\n"
+	 "\n"
+	 "* Examples:\n"
+	 "\n"
+	 "      -J nextsub\n"
+	 "\n"
+	 "          Let the filter guess the indices, rendering text with the default\n"
+	 "          color (white) and the outline as black.  Both are assigned an\n"
+	 "          opacity of 1.0.  You should get something visible, either solid or\n"
+	 "          outlined text, rendered white, and at the very bottom of the screen.\n"
+	 "\n"
+	 "      -J nextsub=clr=d0d000:zoom=0.8\n"
+	 "\n"
+	 "          Similar to the above, but use yellow as the default color and shrink\n"
+	 "          the subtitle by 0.8 x 0.8.\n"
+	 "\n"
+	 "      -J nextsub=o1:o2=0.9:c1=d0d000:c2=00:pos=75:top:aalias:zoom=0.9x0.75\n"
+	 "\n"
+	 "          Specify explicit subtitle indices to use.  Index 1 assumes an\n"
+	 "          opacity of 1.0, and index 2 is specified as 0.9.  Render index 1\n"
+	 "          with yellow, and index 2 with black.  Top align the subtitle at\n"
+	 "          row 75.  Anti-alias it, and zoom to 0.9 x 0.75.\n"
+	 "\n"
+	 "          The above zoom factors can be a good choice for some 16:9 films\n"
+	 "          when zooming a 720x480 frame.  The subtitle is overlayed in the post\n"
+	 "          filter phase and sometimes we need a similar aspect ratio correction\n"
+	 "          applied to the subtitle bitmap as well.  Alternatively, run it as\n"
+	 "          a pre-filter.\n"
+	 "\n"
+	 "      -J nextsub=o2:map=3to2:forced\n"
+	 "\n"
+	 "          Join indices 2 and 3 together to render the text body as index 2.\n"
+	 "          In addition, only render subtitles which have the forced flag set.\n"
+	 "\n"
+	 "      -J nextsub=o1:o2:o3:c1=ff0000:c2=00ff00:c3=0000ff\n"
+	 "\n"
+	 "          Render all indices with a different color.  Encoding just a small\n"
+	 "          number of frames where a subtitle is present, this can be useful\n"
+	 "          for determining what components of the subtitle each index has\n"
+	 "          been assigned for a particular DVD.\n"
+	 "\n"
+	 , MOD_CAP );
+}
+
+
+/*
+ *	tc_filter:
+ *		Single function interface.
+ */
+
+int
+tc_filter( frame_list_t *ptr_, char *opt )
+{
+    vframe_list_t *ptr = (vframe_list_t*) ptr_;
+
+
+    //----------------------------------
+    //
+    // filter init
+    //
+    //----------------------------------
+
+    if ( ptr->tag & TC_FILTER_INIT ) {
+        int n, i;
+
+        mfd = tc_zalloc( sizeof(MyFilterData) );
+        if ( !mfd ) {
+            tc_log_perror( MOD_NAME, "out of memory" );
+            return( TC_EXPORT_ERROR );
+        }
+
+        mfd->vob = tc_get_vob();
+        if ( !mfd->vob )
+            return( -1 );
+
+        mfd->tcvhandle = tcv_init();
+        if ( !mfd->tcvhandle ) {
+            tc_log_error( MOD_NAME, "tcv initialization failed" );
+            return( TC_EXPORT_ERROR );
+        }
+
+        mfd->sub = tc_malloc( SIZE_RGB_FRAME );
+        mfd->tmp = tc_malloc( SIZE_RGB_FRAME );
+        if ( !mfd->sub || !mfd->tmp ) {
+            tc_log_perror( MOD_NAME, "out of memory" );
+            return( TC_EXPORT_ERROR );
+        }
+
+        /* split sub and tmp into individual planes */
+        n = TC_MAX_V_FRAME_WIDTH * TC_MAX_V_FRAME_HEIGHT;
+        for ( i=0; i<3; i++ ) {
+            mfd->sp[i] = mfd->sub + i*n;
+            mfd->dp[i] = mfd->tmp + i*n;
+        }
+
+        mfd->clr_def.r = 255;
+        mfd->clr_def.g = 255;
+        mfd->clr_def.b = 255;
+        for ( i=0; i<3; i++ ) {
+            mfd->clr[i].r = -1;
+            mfd->clr[i].g = -1;
+            mfd->clr[i].b = -1;
+        }
+
+        mfd->pts1 = -1;
+        mfd->pts2 = -1;
+
+        if ( opt != NULL ) {
+            char buf[100];
+            int n;
+
+            if ( verbose )
+                tc_log_info( MOD_NAME, "options=%s", opt );
+
+            /* booleans, default to TRUE if no value is given */
+            n = optstr_get( opt, "forced", "%d", &mfd->forced );
+            if ( !n )
+                mfd->forced = TC_TRUE;
+            n = optstr_get( opt, "top", "%d", &mfd->topalign );
+            if ( !n )
+                mfd->topalign = TC_TRUE;
+            n = optstr_get( opt, "pre", "%d", &mfd->prefilter );
+            if ( !n )
+                mfd->prefilter = TC_TRUE;
+            n = optstr_get( opt, "aalias", "%d", &mfd->antialias );
+            if ( !n )
+                mfd->antialias = TC_TRUE;
+
+            optstr_get( opt, "track", "%d", &mfd->vob->s_track );
+            optstr_get( opt, "pos", "%d", &mfd->pos );
+            optstr_get( opt, "tshift", "%d", &mfd->tshift );
+
+            if ( optstr_get(opt,"zoom","%[^:]",buf) >= 0 ) {
+                if ( get_zoom(buf,&mfd->z_x,&mfd->z_y) )
+                    mfd->do_zoom = 1;
+            }
+
+            /* default these to 1.0 if no value is given */
+            n = optstr_get( opt, "o1", "%lf", &mfd->fopc[0] );
+            if ( !n )
+                mfd->fopc[0] = 1.0;
+            n = optstr_get( opt, "o2", "%lf", &mfd->fopc[1] );
+            if ( !n )
+                mfd->fopc[1] = 1.0;
+            n = optstr_get( opt, "o3", "%lf", &mfd->fopc[2] );
+            if ( !n )
+                mfd->fopc[2] = 1.0;
+
+            if ( optstr_lookup(opt,"o1") ||
+                    optstr_lookup(opt,"o2") ||
+                    optstr_lookup(opt,"o3") )
+                mfd->have_indices = 1;
+
+            if ( optstr_get(opt,"c1","%[^:]",buf) > 0 )
+                mfd->clr[0] = get_rgb( buf );
+            if ( optstr_get(opt,"c2","%[^:]",buf) > 0 )
+                mfd->clr[1] = get_rgb( buf );
+            if ( optstr_get(opt,"c3","%[^:]",buf) > 0 )
+                mfd->clr[2] = get_rgb( buf );
+            if ( optstr_get(opt,"clr","%[^:]",buf) > 0 )
+                mfd->clr_def = get_rgb( buf );
+
+            optstr_get( opt, "map", "%dto%d", &mfd->remap[0], &mfd->remap[1] );
+
+            if ( optstr_lookup(opt,"help") )
+                help_optstr();
+        }
+
+        /* assign default for unset colors */
+        for ( i=0; i<3; i++ )
+            if ( mfd->clr[i].r == -1 )
+                mfd->clr[i] = mfd->clr_def;
+
+        /* convert opacities from 0-1 to 0-255 */
+        for ( i=0; i<3; i++ ) {
+            int o;
+
+            o = mfd->fopc[i] * 255;
+            if ( o < 0 )
+                o = 0;
+            if ( o > 255 )
+                o = 255;
+
+            mfd->opc[i] = o;
+        }
+
+        if ( verbose & TC_DEBUG ) {
+            tc_log_info( MOD_NAME, "Option values:" );
+            tc_log_info( MOD_NAME, "     forced: %d", mfd->forced );
+            tc_log_info( MOD_NAME, "     pos: %d", mfd->pos );
+            tc_log_info( MOD_NAME, "     top: %d", mfd->topalign );
+            tc_log_info( MOD_NAME, "     tshift: %d", mfd->tshift );
+            tc_log_info( MOD_NAME, "     pre: %d", mfd->prefilter );
+            tc_log_info( MOD_NAME, "     aalias: %d", mfd->antialias );
+            tc_log_info( MOD_NAME, "     zoom: %s", pr_zoom() );
+            for ( i=0; i<3; i++ )
+                tc_log_info( MOD_NAME, "     o%d: %g", i+1, mfd->fopc[i] );
+            for ( i=0; i<3; i++ )
+                tc_log_info( MOD_NAME, "     c%d: %s", i+1, pr_clr(mfd->clr[i]) );
+            tc_log_info( MOD_NAME, "     clr: %s", pr_clr(mfd->clr_def) );
+            tc_log_info( MOD_NAME, "     map: %d to %d", mfd->remap[0], mfd->remap[1] );
+        }
+
+        // start subtitle stream
+        mfd->import.flag = TC_SUBEX;
+        if ( tcv_import(TC_IMPORT_OPEN,&mfd->import,mfd->vob) < 0 )
+            tc_log_error( MOD_NAME, "popen subtitle stream" );
+
+        subproc_init( NULL, "title", 0, (unsigned short) mfd->vob->s_track );
+        sframe_alloc( SUBTITLE_BUFFER, mfd->import.fd );
+
+        // start thread
+        if ( pthread_create(&mfd->thread,NULL,(void*)subtitle_reader,NULL) != 0 )
+            tc_log_error( MOD_NAME, "failed to start subtitle import thread" );
+
+        mfd->f_time = 1.0 / (mfd->prefilter ? mfd->vob->fps : mfd->vob->ex_fps);
+
+        if ( verbose )
+            tc_log_info( MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP );
+
+        return( 0 );
+    }
+
+    if ( ptr->tag & TC_FILTER_GET_CONFIG ) {
+        optstr_filter_desc( opt, MOD_NAME, MOD_CAP, MOD_VERSION, MOD_AUTHOR, "VRYOE", "1" );
+        optstr_param( opt, "track", "Subtitle track", "%d", "0", "0", "255" );
+        optstr_param( opt, "forced", "Render only forced subtitles", "%d", "0", "0", "1" );
+        optstr_param( opt, "pos", "Vertical position of subtitle", "%d", "0", "0", "height" );
+        optstr_param( opt, "tshift", "Start time correction (msec)", "%d", "0", "0", "-1" );
+        optstr_param( opt, "pre", "Run as a pre filter", "%d", "0", "0", "1" );
+        optstr_param( opt, "aalias", "Anti-aliasing", "%d", "1", "0", "1" );
+        optstr_param( opt, "zoom", "Zoom subtitle image (XxY)", "%s", "1x1" );
+        optstr_param( opt, "o1", "Index 1 opacity", "%f", "1", "0", "1" );
+        optstr_param( opt, "o2", "Index 2 opacity", "%f", "1", "0", "1" );
+        optstr_param( opt, "o3", "Index 3 opacity", "%f", "1", "0", "1" );
+        optstr_param( opt, "c1", "Index 1 rendering color", "%s", "ffffff" );
+        optstr_param( opt, "c2", "Index 2 rendering color", "%s", "ffffff" );
+        optstr_param( opt, "c3", "Index 3 rendering color", "%s", "ffffff" );
+        optstr_param( opt, "clr", "Default rendering color (hex rrggbb)", "%s", "ffffff" );
+        optstr_param( opt, "map", "Remap an index value (ItoJ)", "%dto%d", "0to0" );
+
+        return( 0 );
+    }
+
+
+    //----------------------------------
+    //
+    // filter close
+    //
+    //----------------------------------
+
+    if ( ptr->tag & TC_FILTER_CLOSE ) {
+        static int close_done;
+        void *status;
+
+        if ( !close_done ) {
+            pthread_cancel( mfd->thread );
+
+#ifdef BROKEN_PTHREADS
+            pthread_cond_signal( &sframe_list_full_cv );
+#endif
+
+            pthread_join( mfd->thread, &status );
+
+            mfd->import.flag = TC_SUBEX;
+            if ( mfd->import.fd != NULL )
+                pclose( mfd->import.fd );
+
+            tcv_free( mfd->tcvhandle );
+            free( mfd->sub );
+            free( mfd->tmp );
+
+            free( mfd );
+
+            close_done = TC_TRUE;
+        }
+
+        return( 0 );
+    }
+
+    //----------------------------------
+    //
+    // filter frame routine
+    //
+    //----------------------------------
+
+    if ( ptr->tag & TC_VIDEO ) {
+        double f_pts;
+
+        if ( mfd->prefilter ) {
+            if ( !(ptr->tag & TC_PRE_S_PROCESS) )
+                return( 0 );
+        } else {
+            if ( !(ptr->tag & TC_POST_S_PROCESS) )
+                return( 0 );
+        }
+
+        if ( verbose & TC_STATS )
+            tc_log_info( MOD_NAME, "%s/%s %s %s",
+                         mfd->vob->mod_path, MOD_NAME, MOD_VERSION, MOD_CAP );
+
+
+        //-------------------------------------------------------------------------
+        //
+        // below is a very fuzzy concept of rendering the subtitle on the movie
+        //
+        // (1) check if we have a valid subtitle and render it
+        // (2) if (1) fails try to get a new one
+        // (3) buffer and display the new one if it's showtime, if not return
+        //
+        // repeat steps throughout the movie
+
+        // calculate current frame video PTS in [s]
+        // adjust for dropped frames so far
+        // adjust for user shift (in milliseconds)
+
+        f_pts = mfd->f_time * (ptr->id-tc_get_frames_dropped()+mfd->vob->psu_offset)
+                + mfd->tshift / 1000.0;
+
+        if ( verbose & TC_DEBUG )
+            tc_log_info( MOD_NAME, "frame=%06d pts=%.3f sub1=%.3f sub2=%.3f",
+                         ptr->id, f_pts, mfd->pts1, mfd->pts2 );
+
+        /* TODO: weird logic here, but going to leave it for now */
+
+        // overlay now?
+        if ( mfd->pts1<=f_pts && f_pts<=mfd->pts2 ) {
+            if ( !mfd->forced || mfd->fflg )
+                subtitle_overlay( ptr->video_buf, ptr->v_width, ptr->v_height );
+            return( 0 );
+        }
+
+        // get a new subtitle, if the last one has expired:
+        if ( f_pts > mfd->pts2 ) {
+            mfd->made_subtitle = 0;
+            if ( subtitle_retrieve() < 0 ) {
+                if ( verbose & TC_DEBUG )
+                    tc_log_info( MOD_NAME, "no subtitle available at this time" );
+                return( 0 );
+            }
+        }
+
+        // overlay now?
+        if ( mfd->pts1<f_pts && f_pts<mfd->pts2 )
+            if ( !mfd->forced || mfd->fflg )
+                subtitle_overlay( ptr->video_buf, ptr->v_width, ptr->v_height );
+    }
+
+    return( 0 );
+}
+
+
+/*
+ *	get_zoom:
+ *		Given a string of the form XxY, where X and Y are floating point
+ *	numbers, return (via parameter) the two scaling values supplied as doubles.
+ *	If only a single value is given, Y assumes the value of X.
+ */
+
+static int
+get_zoom( char *buf, double *px, double *py )
+{
+    double x, y;
+    char *p;
+
+    x = atof( buf );
+    if ( x <= 0 )
+        return( TC_FALSE );
+
+    p = strchr( buf, 'x' );
+    if ( p ) {
+        y = atof( p + 1 );
+        if ( y <= 0 )
+            return( TC_FALSE );
+    } else
+        y = x;
+
+    *px = x;
+    *py = y;
+
+    return( TC_TRUE );
+}
+
+
+/*
+ *	get_rgb:
+ *		Given a string of the form (hex) "rrggbb", return an Rgb structure
+ *	containing the (integer) tuplet [r,g,b].  If only an "rr" component is
+ *	given, gg, and bb, assume the value of rr.  For example, "ff" would be
+ *	interpreted as white (ffffff).  Return the structure by function value.
+ */
+
+static Rgb
+get_rgb( char *buf )
+{
+    Rgb c;
+    int v;
+
+    if ( strlen(buf) < 6 ) {
+        if ( strlen(buf) < 2 )
+            v = 255;
+        else
+            v = htoi( buf );
+        c.r = v;
+        c.g = v;
+        c.b = v;
+
+        return( c );
+    }
+
+    c.r = htoi( buf+0 );
+    c.g = htoi( buf+2 );
+    c.b = htoi( buf+4 );
+
+    return( c );
+}
+
+
+/*
+ *	htoi:
+ *		Convert a 2 digit hex number to integer, and return via function
+ *	value.
+ */
+
+static int
+htoi( char *buf )
+{
+    return( (htob(buf[0]) << 4) + htob(buf[1]) );
+}
+
+
+/*
+ *	htob:
+ *		Convert a hex digit to integer, and return via function value.
+ */
+
+static int
+htob( char c )
+{
+    c = tolower( c );
+    if ( c>='a' && c<='f' )
+        return( c-'a'+10 );
+    return( c-'0' );
+}
+
+
+/*
+ *	pr_zoom:
+ *		Return the zoom x, and y scaling values as a (static) string of
+ *	the form "XxY", via function value.
+ */
+
+static char *
+pr_zoom( void )
+{
+    static char buf[100];
+
+    if ( mfd->do_zoom )
+        tc_snprintf( buf, 100, "%gx%g", mfd->z_x, mfd->z_y );
+    else
+        strcpy( buf, "none" );
+
+    return( buf );
+}
+
+
+/*
+ *	pr_clr:
+ *		Return the given Rgb structure as a string of the form (hex) "rrggbb".
+ */
+
+static char *
+pr_clr( Rgb c )
+{
+    static char buf[100];
+
+    tc_snprintf( buf, 100, "%02x%02x%02x", c.r, c.g, c.b );
+    return( buf );
+}
+
+
+/*
+ *	subtitle_retrieve:
+ *		Retrieve the next subtitle from the subtitle reader thread.
+ *	Store the results in mfd:
+ *
+ *		sub	- subtitle bitmap
+ *		id	- frame number
+ *		pts1	- start time
+ *		pts2	- end time
+ *		fflg	- forced flag
+ *		xp	- suggested x position
+ *		xp	- suggested y position
+ *		sw	- subtitle width
+ *		sh	- subtitle height
+ *
+ *	The function returns 0 if successful, -1 otherwise.
+ */
+
+static int
+subtitle_retrieve( void )
+{
+    sframe_list_t *sptr = NULL;
+    sub_info_t sub;
+
+    // get a subtitle from buffer
+    pthread_mutex_lock( &sframe_list_lock );
+    if ( sframe_fill_level(TC_BUFFER_EMPTY) ) {
+        pthread_mutex_unlock( &sframe_list_lock );
+        return( -1 );
+    }
+
+    // not being empty does not mean ready to go!!!!!
+    if ( sframe_fill_level(TC_BUFFER_READY) ) {
+        pthread_mutex_unlock( &sframe_list_lock );
+        if ( (sptr=sframe_retrieve()) == NULL ) {
+            // this shouldn't happen
+            tc_log_error( MOD_NAME, "internal error (S)" );
+            return( -1 );
+        }
+    } else {
+        pthread_mutex_unlock( &sframe_list_lock );
+        return( -1 );
+    }
+
+    // conversion
+    sub.frame = mfd->sub;
+    if ( subproc_feedme(sptr->video_buf,sptr->video_size,sptr->id,sptr->pts,&sub) < 0 ) {
+        // problems, drop this subtitle
+        if ( verbose & TC_DEBUG )
+            tc_log_warn( MOD_NAME, "subtitle dropped" );
+        sframe_remove( sptr );
+        pthread_cond_signal( &sframe_list_full_cv );
+        return( -1 );
+    }
+
+    // save data
+    mfd->id = sptr->id;
+    mfd->pts1 = sptr->pts * mfd->f_time;
+    mfd->pts2 = mfd->pts1 + sub.time / 100.0;
+
+    mfd->fflg = sub.forced;
+    mfd->orig.x = sub.x;
+    mfd->orig.y = sub.y;
+    mfd->sz.x = sub.w;
+    mfd->sz.y = sub.h;
+
+    // release packet buffer
+    sframe_remove( sptr );
+    pthread_cond_signal( &sframe_list_full_cv );
+
+    if ( verbose & TC_DEBUG )
+        tc_log_info( MOD_NAME, "got SUBTITLE %d with forced=%d, pts=%.3f dtime=%.3f",
+                     mfd->id, mfd->fflg, mfd->pts1, mfd->pts2-mfd->pts1 );
+
+    return( 0 );
+}
+
+
+/*
+ *	subtitle_overlay:
+ *		The current subtitle is within the time window of the current
+ *	video frame.  Overlay it into the image.  If this is the first time
+ *	the subtitle is being rendered, anti-alias it, zoom it, and crop it.
+ *	If this is the very first subtitle rendered, and no opacity arguments
+ *	were given, then guess appropriate indices to use.  Finally, determine
+ *	the subtitle position and overlay the resulting bitmap by blending pixel
+ *	values according to opacity.
+ */
+
+static void
+subtitle_overlay( uint8_t *vid, int w, int h )
+{
+    if ( verbose & TC_DEBUG )
+        tc_log_info( MOD_NAME, "SUBTITLE id=%d, x=%d, y=%d, w=%d, h=%d, t=%f",
+                     mfd->id, mfd->orig.x, mfd->orig.y, mfd->sz.x, mfd->sz.y,
+                     mfd->pts2 - mfd->pts1 );
+
+    if ( !mfd->have_indices ) {
+        /* this is only ever done once, for the first subtitle rendered */
+        mfd->have_indices = TC_TRUE;
+        guess_indices();
+    }
+
+    /* anti-alias, zoom, and crop the subtitle */
+    if ( !mfd->made_subtitle ) {
+        /* this is done once for each subtitle rendered */
+        mfd->made_subtitle = TC_TRUE;
+        make_subtitle( w, h );
+    }
+
+    if ( mfd->vob->im_v_codec == CODEC_RGB )
+        subtitle_overlay_rgb( vid, w, h );
+
+    if ( mfd->vob->im_v_codec == CODEC_YUV )
+        subtitle_overlay_yuv( vid, w, h );
+}
+
+
+/*
+ *	guess_indices:
+ *		This is called if no opacity arguments were given.  The highest
+ *	frequency (non-background) subtitle index is assumed to be the body of
+ *	the text, and the next highest the inner text outline.  Both indices
+ *	are assigned an opacity of 1.0.  The text body is assigned the default
+ *	color, and the outline is assigned as black.
+ */
+
+static void
+guess_indices( void )
+{
+    int subi[4];
+    int ib, io;
+    int n, i;
+
+    for ( i=0; i<4; i++ )
+        subi[i] = 0;
+
+    n = mfd->sz.x * mfd->sz.y;
+    for ( i=0; i<n; i++ )
+        subi[ get_sub_index(i) ]++;
+
+    ib = 1;
+    io = 2;
+    if ( subi[1]>subi[2] && subi[1]>subi[3] ) {
+        ib = 1;
+        io = (subi[2]>subi[3]) ? 2 : 3;
+    }
+    if ( subi[2]>subi[1] && subi[2]>subi[3] ) {
+        ib = 2;
+        io = (subi[1]>subi[3]) ? 1 : 3;
+    }
+    if ( subi[3]>subi[1] && subi[3]>subi[2] ) {
+        ib = 3;
+        io = (subi[1]>subi[2]) ? 1 : 2;
+    }
+
+    --ib;
+    --io;
+
+    mfd->opc[ib] = 255;
+    mfd->opc[io] = 255;
+
+    mfd->clr[io].r = 0;
+    mfd->clr[io].g = 0;
+    mfd->clr[io].b = 0;
+
+    if ( verbose & TC_INFO )
+        tc_log_info( MOD_NAME, "index counts: 0=%d, 1=%d, 2=%d, 3=%d, body=%d, outline=%d",
+                     subi[0], subi[1], subi[2], subi[3],
+                     ib+1, io+1 );
+}
+
+
+/*
+ *	get_sub_index:
+ *		Return, via function value,  the subtitle index value at element i.
+ *	Check for a remapped index, and clamp the result to [0,3].
+ */
+
+static int
+get_sub_index( int i )
+{
+    int c;
+
+    c = mfd->sub[i];
+    if ( c == mfd->remap[0] )
+        c = mfd->remap[1];
+
+    if ( c < 0 )
+        c = 0;
+    if ( c > 3 )
+        c = 3;
+
+    return( c );
+}
+
+
+/*
+ *	make_subtitle:
+ * 		We are given a single NxM plane of indices whose values can be 0 to 3.
+ * 	A value of 0 is always considered background and transparent.  The values 1, 2,
+ * 	and 3 correspond to the text body, inner outline, and outer outline, (non resp).
+ * 	First, the indices are replaced by their corresponding opacity values, with each
+ * 	index written to a separate plane.  These planes are then anti-aliased, and
+ * 	zoomed, Then, a minimum bounding box is computed over all planes (UxV), and the
+ * 	subtitle frame is replaced with the UxV frame (three planes).  We are now capable
+ * 	of rendering the subtitle using different opacity values and rendering colors for
+ * 	each index.  Since the frame has been cropped, we also control where the subtitle
+ * 	is located, regardless of where the original bitmap had placed it.
+ */
+
+static void
+make_subtitle( int w, int h )
+{
+    uint8_t **sp, **dp;
+    Dim sz, p;
+    int n, c, i;
+
+    sp = mfd->sp;
+    dp = mfd->dp;
+
+    /* plane 0 is current subtitle */
+    /* zero planes 1 and 2 */
+    n = mfd->sz.x * mfd->sz.y;
+    memset( sp[1], 0, n );
+    memset( sp[2], 0, n );
+
+    /* replace index values with opacity values */
+    /* store opacities for each index in separate planes */
+    for ( i=0; i<n; i++ ) {
+        c = get_sub_index( i );
+        if ( c ) {
+            --c;
+            sp[c][i] = mfd->opc[c];
+        }
+    }
+
+    /* if anti-aliasing or zooming, crop to reduce work */
+    if ( mfd->antialias || mfd->do_zoom ) {
+        /* crop, with margin 6 */
+        crop_subtitle( sp, dp, mfd->sz, &sz, 6 );
+        cpy3( sp, dp, sz );
+        mfd->sz = sz;
+    }
+
+    if ( mfd->antialias ) {
+        /* anti-alias each plane (individually) */
+        for ( i=0; i<3; i++ )
+            tcv_antialias( mfd->tcvhandle, sp[i], dp[i], mfd->sz.x, mfd->sz.y, 1,
+                           mfd->vob->aa_weight, mfd->vob->aa_bias );
+        cpy3( sp, dp, sz );
+    }
+
+    if ( mfd->do_zoom ) {
+        /* compute zoomed dimensions */
+        sz.x = mfd->sz.x * mfd->z_x + 0.5;
+        sz.y = mfd->sz.y * mfd->z_y + 0.5;
+
+        /* odd dimensions is probably not a good thing */
+        if ( sz.x % 2 )
+            sz.x++;
+        if ( sz.y % 2 )
+            sz.y++;
+
+        /* do not allow the zoom to exceed the dimensions of the video frame */
+        if ( sz.x > w )
+            sz.x = w;
+        if ( sz.y > h )
+            sz.y = h;
+
+        /* zoom each plane (individually) */
+        for ( i=0; i<3; i++ )
+            tcv_zoom( mfd->tcvhandle, sp[i], dp[i], mfd->sz.x, mfd->sz.y,
+                      1, sz.x, sz.y, mfd->vob->zoom_filter );
+
+        cpy3( sp, dp, sz );
+        mfd->sz = sz;
+    }
+
+    /* crop, with margin 0 */
+    crop_subtitle( sp, dp, mfd->sz, &sz, 0 );
+    cpy3( sp, dp, sz );
+    mfd->sz = sz;
+
+    /* find position of overlay region within frame */
+    /*     - zero corresponds to the bottom line of the frame */
+    /*     - if topalign is true, position the top line at pos */
+    /*       (pos should be at least the height of the subtitle) */
+    /*     - if topalign is false, position the bottom line at pos */
+    p.x = (w - mfd->sz.x) / 2;
+    p.y = h - mfd->pos;
+    if ( !mfd->topalign )
+        p.y -= mfd->sz.y;
+
+    /* make the region even aligned */
+    p.x -= p.x % 2;
+    p.y -= p.y % 2;
+
+    /* top clip */
+    mfd->ys = 0;
+    mfd->yn = mfd->sz.y;
+    if ( p.y < 0 ) {
+        mfd->ys = -p.y;
+        mfd->yn += p.y;
+        p.y = 0;
+    }
+
+    /* bottom clip */
+    n = p.y + mfd->sz.y - h;
+    if ( n > 0 )
+        mfd->yn -= n;
+
+    if ( mfd->yn <= 0 )
+        tc_log_warn( MOD_NAME, "Subtitle is complety clipped.  Check the 'pos' option." );
+
+    mfd->orig.x = p.x;
+    mfd->orig.y = p.y;
+}
+
+
+/*
+ *	crop_subtitle:
+ *		Find the minimum bounding box over the three opacity planes
+ *	and copy that region into the dst buffer, (effectively cropping
+ *	the image).  Force both width and height to be a even dimension.
+ *	Return the dimensions of the cropped image via parameter.
+ */
+
+static void
+crop_subtitle( uint8_t **sp, uint8_t **dp, Dim sd, Dim *dd, int margin )
+{
+    Dim ll, ur;
+    int x, y, n, m;
+    int flg, v, i;
+
+    ll.x = 0;
+    ll.y = 0;
+    ur.x = 0;
+    ur.y = 0;
+
+    flg = TC_FALSE;
+    n = 0;
+    for ( y=0; y<sd.y; y++ )
+        for ( x=0; x<sd.x; x++ ) {
+            v = sum3( sp, n );
+            n++;
+
+            if ( !v )
+                continue;
+
+            if ( !flg ) {
+                ll.x = x;
+                ur.x = x;
+                ll.y = y;
+                ur.y = y;
+
+                flg = TC_TRUE;
+                continue;
+            }
+
+            if ( x < ll.x )
+                ll.x = x;
+            if ( x > ur.x )
+                ur.x = x;
+            if ( y < ll.y )
+                ll.y = y;
+            if ( y > ur.y )
+                ur.y = y;
+        }
+
+    /* add margin */
+    ll.x -= margin;
+    ll.y -= margin;
+    ur.x += margin;
+    ur.y += margin;
+
+    /* force even dimensions */
+    dd->x = ur.x - ll.x + 1;
+    dd->y = ur.y - ll.y + 1;
+    if ( dd->x % 2 ) {
+        ur.x++;
+        dd->x++;
+    }
+
+    if ( dd->y % 2 ) {
+        ur.y++;
+        dd->y++;
+    }
+
+    /* extract bounded region to tmp, cropping, possibly padding */
+    m = 0;
+    for ( y=ll.y; y<=ur.y; y++ )
+        for ( x=ll.x; x<=ur.x; x++, m++ ) {
+            if ( x<0 || x>=sd.x || y<0 || y>=sd.y )
+                n = -1;
+            else
+                n = y*sd.x + x;
+
+            for ( i=0; i<3; i++ )
+                dp[i][m] = (n<0) ? 0 : sp[i][n];
+        }
+}
+
+
+/*
+ *	sum3:
+ *		Return the sum of the three opacity planes for element n.
+ */
+
+static int
+sum3( uint8_t **sp, int n )
+{
+    int s, i;
+
+    s = 0;
+    for ( i=0; i<3; i++ )
+        s += sp[i][n];
+
+    return( s );
+}
+
+
+/*
+ *	max3:
+ *		Return the maximum opacity value for element n, over the three
+ *	opacity planes.
+ */
+
+static int
+max3( uint8_t **sp, int n )
+{
+    int v, i;
+
+    v = 0;
+    for ( i=0; i<3; i++ )
+        if ( sp[i][n] > v )
+            v = sp[i][n];
+
+    return( v );
+}
+
+
+/*
+ *	avg3:
+ *		Return the weighted average for the RGB rendering colors at element n,
+ *	as determined by the contributing opacity values on each plane.
+ */
+
+static Rgb
+avg3( uint8_t **sp, int n )
+{
+    Rgb clr;
+    double s[3];
+    int w, i;
+
+    w = sum3( sp, n );
+    for ( i=0; i<3; i++ )
+        s[i] = sp[i][n] / (double) w;
+
+    /* I'm thinking Rgb would have been better as an array */
+    clr.r = 0;
+    clr.g = 0;
+    clr.b = 0;
+    for ( i=0; i<3; i++ ) {
+        clr.r += mfd->clr[i].r * s[i] + 0.5;
+        clr.g += mfd->clr[i].g * s[i] + 0.5;
+        clr.b += mfd->clr[i].b * s[i] + 0.5;
+    }
+
+    if ( clr.r > 255 )
+        clr.r = 255;
+    if ( clr.g > 255 )
+        clr.g = 255;
+    if ( clr.b > 255 )
+        clr.b = 255;
+
+    return( clr );
+}
+
+
+/*
+ *	cpy3:
+ *		Copy src to dst, for each of the three planes.
+ */
+
+static void
+cpy3( uint8_t **sp, uint8_t **dp, Dim sz )
+{
+    int n, i;
+
+    n = sz.x * sz.y;
+    for ( i=0; i<3; i++ )
+        ac_memcpy( sp[i], dp[i], n );
+}
+
+
+/*
+ *	subtitle_overlay_rgb:
+ *		Overlay the subtitle bitmap into the given video frame.  For each pixel,
+ *	the opacity used is the maximum value over all index planes.  The color used is
+ *	a weighted average as determined by the contributing opacity values on each
+ *	plane.  The resulting color is then blended with the pixel, ie:
+ *
+ *		(1-opacity) * pixel_color + opacity * subtitle_color
+ */
+
+static void
+subtitle_overlay_rgb( uint8_t *vid, int w, int h )
+{
+    uint8_t *v;
+    Rgb clr;
+    double s;
+    int x, y, n, m;
+    int opc, yo;
+
+    for ( y=0; y<mfd->yn; y++ ) {
+        yo = mfd->orig.y + y;
+        m = (yo*w + mfd->orig.x) * 3;
+
+        n = (mfd->ys + y) * mfd->sz.x;
+        for ( x=0; x<mfd->sz.x; x++ ) {
+            opc = max3( mfd->sp, n );
+            if ( opc ) {
+                clr = avg3( mfd->sp, n );
+                s = opc / 255.0;
+                if ( s > 1.0 )
+                    s = 1.0;
+
+                v = vid + m;
+                blend_pixel( v+0, s, clr.r );
+                blend_pixel( v+1, s, clr.g );
+                blend_pixel( v+2, s, clr.b );
+            }
+
+            m += 3;
+            n++;
+        }
+    }
+}
+
+
+/*
+ *	subtitle_overlay_yuv:
+ *		Trying to blend in pixels in a non-linear color space just doesn't
+ *	produce aesthetically pleasing results.  Many variations were attempted.
+ *	At edges of the text, even a slightly opaque pixel would cause the UV component
+ *	for the entire 4x4 pixel cell to be modified, which generated very visable
+ *	artifacts.  Therefore, although quite expensive, the overlay region of the
+ *	video frame is extracted to a temporary buffer, converted to rgb, overlayed
+ *	with the subtitle, converted back to yuv, and re-inserted back into the
+ *	video frame.  The results are better, but still not as good as just running
+ *	with -V rgb24.  When the sub region is converted back to yuv, you still get
+ *	some color blurring around edges of sharp contrast.  You really have to be
+ *	looking for it to notice though.
+ */
+
+static void
+subtitle_overlay_yuv( uint8_t *vid, int w, int h )
+{
+    uint8_t *sp, *dp, *v;
+    Rgb clr;
+    double s;
+    int sw, sh, sn, sy, su, sv;
+    int dw, dh, dn, dy, du, dv;
+    int x, y, r;
+    int opc, n, m;
+
+    if ( mfd->yn <= 0 )
+        /* completely clipped */
+        return;
+
+    /* src, video frame */
+    sp = vid;
+    sw = w;
+    sh = h;
+    sn = sw * sh;
+
+    sy = mfd->orig.y * sw + mfd->orig.x;
+    su = sn + mfd->orig.y/2 * sw/2 + mfd->orig.x/2;
+    sv = su + sn/4;
+
+    /* dst, extracted region */
+    dp = mfd->tmp;
+    dw = mfd->sz.x;
+    dh = mfd->yn;
+    dn = dw * dh;
+
+    dy = 0;
+    du = dn;
+    dv = du + dn/4;
+
+    /* extract subtitle region */
+    for ( y=0; y<dh; y++, sy+=sw, dy+=dw ) {
+        ac_memcpy( dp+dy, sp+sy, dw );
+
+        if ( !(y % 2) ) {
+            ac_memcpy( dp+du, sp+su, dw/2 );
+            ac_memcpy( dp+dv, sp+sv, dw/2 );
+
+            su += sw/2;
+            sv += sw/2;
+
+            du += dw/2;
+            dv += dw/2;
+        }
+    }
+
+    /* convert to rgb */
+    r = tcv_convert( mfd->tcvhandle, dp, dp, dw, dh,
+                     IMG_YUV_DEFAULT, IMG_RGB_DEFAULT );
+    if ( !r ) {
+        tc_log_error( MOD_NAME, "overlay, yuv to rgb conversion failed" );
+        return;
+    }
+
+    /* overlay subtitle */
+    m = 0;
+    n = mfd->ys * dw;
+    for ( y=0; y<dh; y++ )
+        for ( x=0; x<dw; x++, m+=3, n++ ) {
+            opc = max3( mfd->sp, n );
+            if ( opc ) {
+                clr = avg3( mfd->sp, n );
+                s = opc / 255.0;
+                if ( s > 1.0 )
+                    s = 1.0;
+
+                v = dp + m;
+                blend_pixel( v+0, s, clr.r );
+                blend_pixel( v+1, s, clr.g );
+                blend_pixel( v+2, s, clr.b );
+            }
+        }
+
+    /* convert back to yuv */
+    r = tcv_convert( mfd->tcvhandle, dp, dp, dw, dh,
+                     IMG_RGB_DEFAULT, IMG_YUV_DEFAULT );
+    if ( !r ) {
+        tc_log_error( MOD_NAME, "overlay, rgb to yuv conversion failed" );
+        return;
+    }
+
+    /* re-insert extracted region */
+    sy -= dh * sw;
+    su -= dh * sw / 4;
+    sv -= dh * sw / 4;
+
+    dy -= dh * dw;
+    du -= dh * dw / 4;
+    dv -= dh * dw / 4;
+
+    for ( y=0; y<dh; y++, sy+=sw, dy+=dw ) {
+        ac_memcpy( sp+sy, dp+dy, dw );
+
+        if ( !(y % 2) ) {
+            ac_memcpy( sp+su, dp+du, dw/2 );
+            ac_memcpy( sp+sv, dp+dv, dw/2 );
+
+            su += sw/2;
+            sv += sw/2;
+
+            du += dw/2;
+            dv += dw/2;
+        }
+    }
+}
+
+
+/*
+ *	blend_pixel:
+ *		Blend in a subtitle color component against the current pixel color
+ *	component, according to the subtitle opacity value.
+ */
+
+static void
+blend_pixel( uint8_t *v, double s, int color )
+{
+    int t;
+
+    t = (1-s)*(*v) + s*color + 0.5;
+    if ( t > 255 )
+        t = 255;
+
+    *v = t;
+}
diff -ruN old/filter/extsub/nextsub.h new/filter/extsub/nextsub.h
--- old/filter/extsub/nextsub.h	1969-12-31 19:00:00.000000000 -0500
+++ new/filter/extsub/nextsub.h	2007-03-11 22:24:26.000000000 -0400
@@ -0,0 +1,105 @@
+#include "transcode.h"
+#include "encoder.h"
+#include "filter.h"
+#include "libtc/optstr.h"
+#include "libtcvideo/tcvideo.h"
+
+#include <stdint.h>
+#include <ctype.h>
+
+#include "dl_loader.h"
+#include "import/magic.h"
+
+#include "subtitle_buffer.h"
+#include "subproc.h"
+
+#define BUFFER_SIZE SIZE_RGB_FRAME
+#define SUBTITLE_BUFFER 100
+
+
+typedef struct rgb_t Rgb;
+struct rgb_t
+    {
+    int	r;
+    int	g;
+    int	b;
+    };
+
+typedef struct dim_t Dim;
+struct dim_t
+	{
+	int x;
+	int y;
+	};
+
+typedef struct MyFilterData_t MyFilterData;
+struct MyFilterData_t
+    {
+    /* filter options */
+    int	forced;			/* render only forced subtitles */
+    int	pos;			/* position, vertical offset */
+    int	topalign;		/* align via top (not bottom) row of the subtitle */
+    int	tshift;			/* time shift */
+    int	prefilter;		/* run as a pre-filter */
+    int	antialias;		/* anti-alias */
+    int	do_zoom;		/* zoom the subtitle */
+    double z_x, z_y;		/* zoom factors */
+    double fopc[3];		/* index opacities [0-1] */
+    Rgb	clr[3];			/* index rendering colors */
+    Rgb	clr_def;		/* default rendering color */
+    int remap[2];		/* remap an index value to a different index */
+
+    /* private data */
+    transfer_t import;
+    pthread_t thread;
+    double f_time;
+    double pts1;
+    double pts2;
+
+    uint8_t *sub;		/* subtitle frame */
+    uint8_t *tmp;		/* temp buffer */
+
+    uint8_t *sp[3];		/* sub, decomposed to three planes */
+    uint8_t *dp[3];		/* tmp, decomposed to three planes */
+
+    Dim orig;			/* origin of clipped overlay region in video */
+    Dim sz;			/* dimension of subtitle */
+    int ys;			/* starting on-screen row of subtitle */
+    int yn;			/* number of on-screen subtitle rows */
+
+    int id;			/* frame number */
+    int fflg;			/* subtitle has forced flag set */
+    int opc[3];			/* opacities [0-255] */
+
+    int have_indices;		/* indices to render are known */
+    int made_subtitle;		/* subtitle has been prepared (zoomed, etc) */
+
+    vob_t *vob;
+    TCVHandle tcvhandle;
+    };
+
+
+/*
+ *	prototypes:
+ */
+
+static int get_zoom( char*, double*, double* );
+static Rgb get_rgb( char* );
+static int htoi( char* );
+static int htob( char );
+static char *pr_zoom( void );
+static char *pr_clr( Rgb );
+static int subtitle_retrieve( void );
+static void subtitle_overlay( uint8_t*, int, int );
+static void guess_indices( void );
+static int get_sub_index( int );
+static void make_subtitle( int, int );
+static void crop_subtitle( uint8_t**, uint8_t**, Dim, Dim*, int );
+static int sum3( uint8_t**, int );
+static int max3( uint8_t**, int );
+static Rgb avg3( uint8_t**, int );
+static void cpy3( uint8_t**, uint8_t**, Dim );
+static void subtitle_overlay_rgb( uint8_t*, int, int );
+static void subtitle_overlay_yuv( uint8_t*, int, int );
+static void blend_pixel( uint8_t*, double, int );
+
diff -ruN old/filter/extsub/subproc.c new/filter/extsub/subproc.c
--- old/filter/extsub/subproc.c	2007-03-11 22:25:51.000000000 -0400
+++ new/filter/extsub/subproc.c	2007-03-11 22:24:26.000000000 -0400
@@ -178,7 +178,7 @@
   //char filename[16];
 
   //buffer for plugin
-  unsigned char *picture;
+  uint8_t *picture;
 
   picture = config.sub.frame;
 
diff -ruN old/filter/extsub/subproc.h new/filter/extsub/subproc.h
--- old/filter/extsub/subproc.h	2007-03-11 22:25:51.000000000 -0400
+++ new/filter/extsub/subproc.h	2007-03-11 22:24:26.000000000 -0400
@@ -39,7 +39,7 @@
   int x, y;
   int w, h;
 
-  char *frame;
+  uint8_t *frame;
 
   int colour[4];
   int alpha[4];