From 85071f3419dec4837538a25f0ab8853cd6ddf531 Mon Sep 17 00:00:00 2001 From: Gildas Bazin Date: Wed, 6 Nov 2002 21:48:24 +0000 Subject: [PATCH] * modules/codec/spudec/*: modified the spu decoder to handle text subtitles. Only one format of text subtitles is supported right now but we should be able to expand this by modifying modules/codec/spudec/text.c. Most of this work comes from by Andrew Flintham ( thanks a bunch Andrew :). * share/font-eutopiabold36.rle: new font for the text subtitler, courtesy of Andrew Flintham. * AUTHORS: added Andrew Flintham to the authors file. * modules/demux/ogg.c: modified the ogg demuxer to handle subtitles. * modules/codec/ffmpeg/*: modified the ffmpeg decoder to always keep the last decoded frame linked. --- AUTHORS | 4 + modules/codec/ffmpeg/video.c | 18 +- modules/codec/ffmpeg/video.h | 5 +- modules/codec/spudec/Modules.am | 4 +- modules/codec/spudec/parse.c | 10 +- modules/codec/spudec/render.c | 10 +- modules/codec/spudec/spudec.c | 78 +++-- modules/codec/spudec/spudec.h | 20 +- modules/codec/spudec/subtitler.c | 577 +++++++++++++++++++++++++++++++ modules/codec/spudec/text.c | 85 +++++ modules/demux/ogg.c | 72 ++-- share/font-eutopiabold36.rle | Bin 0 -> 45242 bytes 12 files changed, 814 insertions(+), 69 deletions(-) create mode 100644 modules/codec/spudec/subtitler.c create mode 100644 modules/codec/spudec/text.c create mode 100644 share/font-eutopiabold36.rle diff --git a/AUTHORS b/AUTHORS index c81a62d535..48338788d4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -419,3 +419,7 @@ C: ipkiss D: Win32 interface S: France +N: Andrew Flintham +E: amf@cus.org.uk +D: text subtitler and font scripts +S: United Kingdom diff --git a/modules/codec/ffmpeg/video.c b/modules/codec/ffmpeg/video.c index 99a2ecdce3..068214a1e3 100644 --- a/modules/codec/ffmpeg/video.c +++ b/modules/codec/ffmpeg/video.c @@ -2,7 +2,7 @@ * video.c: video decoder using ffmpeg library ***************************************************************************** * Copyright (C) 1999-2001 VideoLAN - * $Id: video.c,v 1.2 2002/11/05 10:07:56 gbazin Exp $ + * $Id: video.c,v 1.3 2002/11/06 21:48:24 gbazin Exp $ * * Authors: Laurent Aimar * Gildas Bazin @@ -349,6 +349,8 @@ int E_( InitThread_Video )( vdec_thread_t *p_vdec ) p_vdec->b_hurry_up = config_GetInt(p_vdec->p_fifo, "ffmpeg-hurry-up"); + p_vdec->p_lastpic = NULL; + p_vdec->p_secondlastpic = NULL; p_vdec->b_direct_rendering = 0; #if LIBAVCODEC_BUILD > 4615 if( (p_vdec->p_codec->capabilities & CODEC_CAP_DR1) @@ -668,6 +670,11 @@ void E_( DecodeThread_Video )( vdec_thread_t *p_vdec ) *****************************************************************************/ void E_( EndThread_Video )( vdec_thread_t *p_vdec ) { + if( p_vdec->p_secondlastpic ) + vout_UnlinkPicture( p_vdec->p_vout, p_vdec->p_secondlastpic ); + if( p_vdec->p_lastpic ) + vout_UnlinkPicture( p_vdec->p_vout, p_vdec->p_lastpic ); + if( p_vdec->p_pp ) { /* release postprocessing module */ @@ -797,8 +804,13 @@ static int ffmpeg_GetFrameBuf( struct AVCodecContext *avctx, int width, msleep( VOUT_OUTMEM_SLEEP ); } - /* FIXME: we may have to use link/unlinkPicture to fully support streams - * with B FRAMES */ + /* FIXME: We keep the last picture linked until the current one is decoded, + * this trick won't work with streams with B frames though. */ + vout_LinkPicture( p_vdec->p_vout, p_pic ); + if( p_vdec->p_secondlastpic ) + vout_UnlinkPicture( p_vdec->p_vout, p_vdec->p_secondlastpic ); + p_vdec->p_secondlastpic = p_vdec->p_lastpic; + p_vdec->p_lastpic = p_pic; avctx->draw_horiz_band= NULL; avctx->dr_buffer[0]= p_pic->p[0].p_pixels; diff --git a/modules/codec/ffmpeg/video.h b/modules/codec/ffmpeg/video.h index bd1bdbcfd7..497c4b2531 100644 --- a/modules/codec/ffmpeg/video.h +++ b/modules/codec/ffmpeg/video.h @@ -2,7 +2,7 @@ * video.h: video decoder using ffmpeg library ***************************************************************************** * Copyright (C) 1999-2001 VideoLAN - * $Id: video.h,v 1.2 2002/11/05 10:07:56 gbazin Exp $ + * $Id: video.h,v 1.3 2002/11/06 21:48:24 gbazin Exp $ * * Authors: Laurent Aimar * @@ -41,7 +41,8 @@ typedef struct vdec_thread_s /* for direct rendering */ int b_direct_rendering; - + picture_t *p_lastpic; + picture_t *p_secondlastpic; } vdec_thread_t; diff --git a/modules/codec/spudec/Modules.am b/modules/codec/spudec/Modules.am index 8c5deab2b5..78a219d899 100644 --- a/modules/codec/spudec/Modules.am +++ b/modules/codec/spudec/Modules.am @@ -1,7 +1,9 @@ SOURCES_spudec = \ modules/codec/spudec/spudec.c \ modules/codec/spudec/parse.c \ - modules/codec/spudec/render.c + modules/codec/spudec/render.c \ + modules/codec/spudec/text.c \ + modules/codec/spudec/subtitler.c noinst_HEADERS += \ modules/codec/spudec/spudec.h diff --git a/modules/codec/spudec/parse.c b/modules/codec/spudec/parse.c index 02e2cd48da..7b72cb3845 100644 --- a/modules/codec/spudec/parse.c +++ b/modules/codec/spudec/parse.c @@ -2,7 +2,7 @@ * parse.c: SPU parser ***************************************************************************** * Copyright (C) 2000-2001 VideoLAN - * $Id: parse.c,v 1.4 2002/11/06 18:07:57 sam Exp $ + * $Id: parse.c,v 1.5 2002/11/06 21:48:24 gbazin Exp $ * * Authors: Samuel Hocevar * @@ -31,14 +31,6 @@ #include #include -#ifdef HAVE_UNISTD_H -# include /* getpid() */ -#endif - -#ifdef WIN32 /* getpid() for win32 is located in process.h */ -# include -#endif - #include "spudec.h" /***************************************************************************** diff --git a/modules/codec/spudec/render.c b/modules/codec/spudec/render.c index c913280b71..60bec344be 100644 --- a/modules/codec/spudec/render.c +++ b/modules/codec/spudec/render.c @@ -2,7 +2,7 @@ * render.c : SPU renderer ***************************************************************************** * Copyright (C) 2000-2001 VideoLAN - * $Id: render.c,v 1.3 2002/11/06 18:07:57 sam Exp $ + * $Id: render.c,v 1.4 2002/11/06 21:48:24 gbazin Exp $ * * Authors: Samuel Hocevar * Rudolf Cornelissen @@ -33,14 +33,6 @@ #include #include -#ifdef HAVE_UNISTD_H -# include /* getpid() */ -#endif - -#ifdef WIN32 /* getpid() for win32 is located in process.h */ -# include -#endif - #include "spudec.h" /***************************************************************************** diff --git a/modules/codec/spudec/spudec.c b/modules/codec/spudec/spudec.c index 03383909a5..50da46991f 100644 --- a/modules/codec/spudec/spudec.c +++ b/modules/codec/spudec/spudec.c @@ -2,7 +2,7 @@ * spudec.c : SPU decoder thread ***************************************************************************** * Copyright (C) 2000-2001 VideoLAN - * $Id: spudec.c,v 1.7 2002/11/06 18:07:57 sam Exp $ + * $Id: spudec.c,v 1.8 2002/11/06 21:48:24 gbazin Exp $ * * Authors: Samuel Hocevar * @@ -31,14 +31,6 @@ #include #include -#ifdef HAVE_UNISTD_H -# include /* getpid() */ -#endif - -#ifdef WIN32 /* getpid() for win32 is located in process.h */ -# include -#endif - #include "spudec.h" /***************************************************************************** @@ -52,8 +44,16 @@ static void EndThread ( spudec_thread_t * ); /***************************************************************************** * Module descriptor. *****************************************************************************/ +#define FONT_TEXT N_("Font used by the text subtitler") +#define FONT_LONGTEXT N_(\ + "When the subtitles are coded in text form then, you can choose " \ + "which font will be used to display them.") + vlc_module_begin(); - set_description( _("DVD subtitles decoder module") ); + add_category_hint( N_("subtitles"), NULL ); + add_file( "spudec-font", "./share/font-eutopiabold36.rle", NULL, + FONT_TEXT, FONT_LONGTEXT ); + set_description( _("subtitles decoder module") ); set_capability( "decoder", 50 ); set_callbacks( OpenDecoder, NULL ); vlc_module_end(); @@ -69,11 +69,12 @@ static int OpenDecoder( vlc_object_t *p_this ) decoder_fifo_t *p_fifo = (decoder_fifo_t*) p_this; if( p_fifo->i_fourcc != VLC_FOURCC('s','p','u',' ') - && p_fifo->i_fourcc != VLC_FOURCC('s','p','u','b') ) - { + && p_fifo->i_fourcc != VLC_FOURCC('s','p','u','b') + && p_fifo->i_fourcc != VLC_FOURCC('s','u','b','t') ) + { return VLC_EGENERIC; } - + p_fifo->pf_run = RunDecoder; return VLC_SUCCESS; @@ -85,6 +86,8 @@ static int OpenDecoder( vlc_object_t *p_this ) static int RunDecoder( decoder_fifo_t * p_fifo ) { spudec_thread_t * p_spudec; + subtitler_font_t * p_font; + char * psz_font; /* Allocate the memory needed to store the thread's structure */ p_spudec = (spudec_thread_t *)malloc( sizeof(spudec_thread_t) ); @@ -101,7 +104,7 @@ static int RunDecoder( decoder_fifo_t * p_fifo ) */ p_spudec->p_vout = NULL; p_spudec->p_fifo = p_fifo; - + /* * Initialize thread and free configuration */ @@ -111,14 +114,48 @@ static int RunDecoder( decoder_fifo_t * p_fifo ) * Main loop - it is not executed if an error occured during * initialization */ - while( (!p_spudec->p_fifo->b_die) && (!p_spudec->p_fifo->b_error) ) + if( p_fifo->i_fourcc == VLC_FOURCC('s','u','b','t') ) { - if( E_(SyncPacket)( p_spudec ) ) + /* Here we are dealing with text subtitles */ + + if( (psz_font = config_GetPsz( p_fifo, "spudec-font" )) == NULL ) + { + msg_Err( p_fifo, "no default font selected" ); + p_font = NULL; + p_spudec->p_fifo->b_error; + } + else { - continue; + p_font = subtitler_LoadFont( p_spudec->p_vout, psz_font ); + if ( p_font == NULL ) + { + msg_Err( p_fifo, "unable to load font: %s", psz_font ); + p_spudec->p_fifo->b_error; + } } + if( psz_font ) free( psz_font ); - E_(ParsePacket)( p_spudec ); + while( (!p_spudec->p_fifo->b_die) && (!p_spudec->p_fifo->b_error) ) + { + E_(ParseText)( p_spudec, p_font ); + } + + if( p_font ) subtitler_UnloadFont( p_spudec->p_vout, p_font ); + + } + else + { + /* Here we are dealing with sub-pictures subtitles*/ + + while( (!p_spudec->p_fifo->b_die) && (!p_spudec->p_fifo->b_error) ) + { + if( E_(SyncPacket)( p_spudec ) ) + { + continue; + } + + E_(ParsePacket)( p_spudec ); + } } /* @@ -154,8 +191,8 @@ static int InitThread( spudec_thread_t *p_spudec ) { if( p_spudec->p_fifo->b_die || p_spudec->p_fifo->b_error ) { - /* Call InitBitstream anyway so p_spudec is in a known state - * before calling CloseBitstream */ + /* Call InitBitstream anyway so p_spudec->bit_stream is in a known + * state before calling CloseBitstream */ InitBitstream( &p_spudec->bit_stream, p_spudec->p_fifo, NULL, NULL ); return -1; @@ -209,4 +246,3 @@ static void EndThread( spudec_thread_t *p_spudec ) CloseBitstream( &p_spudec->bit_stream ); free( p_spudec ); } - diff --git a/modules/codec/spudec/spudec.h b/modules/codec/spudec/spudec.h index 887380e787..53d434a700 100644 --- a/modules/codec/spudec/spudec.h +++ b/modules/codec/spudec/spudec.h @@ -2,7 +2,7 @@ * spudec.h : sub picture unit decoder thread interface ***************************************************************************** * Copyright (C) 1999, 2000 VideoLAN - * $Id: spudec.h,v 1.3 2002/11/06 18:07:57 sam Exp $ + * $Id: spudec.h,v 1.4 2002/11/06 21:48:24 gbazin Exp $ * * Authors: Samuel Hocevar * @@ -44,6 +44,18 @@ struct subpicture_sys_t int i_x_start, i_y_start, i_x_end, i_y_end; }; +/***************************************************************************** + * subtitler_font_t : proportional font + *****************************************************************************/ +typedef struct subtitler_font_s +{ + int i_height; /* character height in pixels */ + int i_width[256]; /* character widths in pixels */ + int i_memory[256]; /* amount of memory used by character */ + int * p_length[256]; /* line byte widths */ + u16 ** p_offset[256]; /* pointer to RLE data */ +} subtitler_font_t; + /***************************************************************************** * spudec_thread_t : sub picture unit decoder thread descriptor *****************************************************************************/ @@ -99,3 +111,9 @@ void E_(ParsePacket) ( spudec_thread_t * ); void E_(RenderSPU) ( vout_thread_t *, picture_t *, const subpicture_t * ); +void E_(ParseText) ( spudec_thread_t *, subtitler_font_t * ); + +subtitler_font_t *E_(subtitler_LoadFont) ( vout_thread_t *, const char * ); +void E_(subtitler_UnloadFont) ( vout_thread_t *, subtitler_font_t * ); +void E_(subtitler_PlotSubtitle) ( vout_thread_t *, char *, subtitler_font_t *, + mtime_t, mtime_t ); diff --git a/modules/codec/spudec/subtitler.c b/modules/codec/spudec/subtitler.c new file mode 100644 index 0000000000..9d10405553 --- /dev/null +++ b/modules/codec/spudec/subtitler.c @@ -0,0 +1,577 @@ +/***************************************************************************** + * subtitler.c : subtitler font routines + ***************************************************************************** + * Copyright (C) 1999, 2000 VideoLAN + * + * Authors: Andrew Flintham + * + * This program 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 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#include /* malloc(), free() */ +#include /* memcpy(), memset() */ +#include /* errno */ +#include /* open() */ +#include /* toascii() */ + +#include +#include +#include + +#ifdef HAVE_UNISTD_H +# include /* read(), close() */ +#endif + +#include +#include + +#include "spudec.h" + +/***************************************************************************** + * subtitler_line : internal structure for an individual line in a subtitle + *****************************************************************************/ +typedef struct subtitler_line_s +{ + struct subtitler_line_s * p_next; + char * p_text; +} subtitler_line_t; + +/***************************************************************************** + * Local prototypes + *****************************************************************************/ +static uint16_t *PlotSubtitleLine( char *, subtitler_font_t *, int, + uint16_t * ); +static void DestroySPU ( subpicture_t * ); + +/***************************************************************************** + * subtitler_LoadFont: load a run-length encoded font file into memory + ***************************************************************************** + * RLE font files have the following format: + * + * 2 bytes : magic number: 0x36 0x05 + * 1 byte : font height in rows + * + * then, per character: + * 1 byte : character + * 1 byte : character width in pixels + * + * then, per row: + * 1 byte : length of row, in entries + * + * then, per entry + * 1 byte : colour + * 1 byte : number of pixels of that colour + * + * to end: + * 1 byte : 0xff + *****************************************************************************/ +subtitler_font_t* subtitler_LoadFont( vout_thread_t * p_vout, + const char * psz_name ) +{ + subtitler_font_t * p_font; + + int i; + int i_file; + int i_char; + int i_length; + int i_line; + int i_total_length; + + byte_t pi_buffer[512]; /* file buffer */ + + msg_Dbg( p_vout, "loading font '%s'", psz_name ); + + i_file = open( psz_name, O_RDONLY ); + + if( i_file == -1 ) + { + msg_Err( p_vout, "can't open font file '%s' (%s)", psz_name, + strerror(errno) ); + return( NULL ); + } + + /* Read magick number */ + if( read( i_file, pi_buffer, 2 ) != 2 ) + { + msg_Err( p_vout, "unexpected end of font file '%s'", psz_name ); + close( i_file ); + return( NULL ); + } + if( pi_buffer[0] != 0x36 || pi_buffer[1] != 0x05 ) + { + msg_Err( p_vout, "file '%s' is not a font file", psz_name ); + close( i_file ); + return( NULL ); + } + + p_font = malloc( sizeof( subtitler_font_t ) ); + + if( p_font == NULL ) + { + msg_Err( p_vout, "out of memory" ); + close( i_file ); + return NULL; + } + + /* Read font height */ + if( read( i_file, pi_buffer, 1 ) != 1 ) + { + msg_Err( p_vout, "unexpected end of font file '%s'", psz_name ); + free( p_font ); + close( i_file ); + return( NULL ); + } + p_font->i_height = pi_buffer[0]; + + /* Initialise font character data */ + for( i = 0; i < 256; i++ ) + { + p_font->i_width[i] = 0; + p_font->i_memory[i] = 0; + p_font->p_offset[i] = NULL; + p_font->p_length[i] = NULL; + } + + while(1) + { + /* Read character number */ + if( read( i_file, pi_buffer, 1 ) != 1) + { + msg_Err( p_vout, "unexpected end of font file '%s'", psz_name ); + close( i_file ); + subtitler_UnloadFont( p_vout, p_font ); + return( NULL ); + } + i_char = pi_buffer[0]; + + /* Character 255 signals the end of the font file */ + if(i_char == 255) + { + break; + } + + /* Read character width */ + if( read( i_file, pi_buffer, 1 ) != 1 ) + { + msg_Err( p_vout, "unexpected end of font file '%s'", psz_name ); + close( i_file ); + subtitler_UnloadFont( p_vout, p_font ); + return( NULL ); + } + p_font->i_width[ i_char ] = pi_buffer[0]; + + p_font->p_length[ i_char ] = (int *) malloc( + sizeof(int) * p_font->i_height ); + p_font->p_offset[ i_char ] = (uint16_t **) malloc( + sizeof(uint16_t *) * p_font->i_height); + + if( p_font->p_length[ i_char] == NULL || + p_font->p_offset[ i_char ] == NULL ) + { + msg_Err( p_vout, "out of memory" ); + close( i_file ); + subtitler_UnloadFont( p_vout, p_font ); + return NULL; + } + for( i_line=0; i_line < p_font->i_height; i_line ++ ) + { + p_font->p_offset[ i_char ][ i_line ] = NULL; + } + + i_total_length=0; + for( i_line = 0; i_line < p_font->i_height; i_line ++ ) + { + /* Read line length */ + if( read( i_file, pi_buffer, 1 ) != 1) + { + msg_Err( p_vout, "unexpected end of font file '%s'", psz_name); + subtitler_UnloadFont( p_vout, p_font ); + close( i_file ); + return( NULL ); + } + i_length = pi_buffer[0]; + p_font->p_length[ i_char ][ i_line ] = i_length; + + i_total_length += i_length; + + /* Read line RLE data */ + if( read( i_file, pi_buffer, i_length*2 ) != i_length*2) + { + msg_Err( p_vout, "unexpected end of font file '%s'", psz_name); + subtitler_UnloadFont( p_vout, p_font ); + close( i_file ); + return( NULL ); + } + p_font->p_offset[ i_char ][ i_line ] = + (uint16_t *) malloc( sizeof( uint16_t ) * i_length ); + if( p_font->p_offset[ i_char ][ i_line ] == NULL ) + { + msg_Err( p_vout, "out of memory" ); + close( i_file ); + subtitler_UnloadFont( p_vout, p_font ); + return NULL; + } + for( i = 0; i < i_length; i++ ) + { + *( p_font->p_offset[ i_char ][ i_line ] + i ) = + (uint16_t) ( pi_buffer[ i * 2 ] + + ( pi_buffer[ i * 2 + 1 ] << 2 ) ); + } + + } + + /* Set total memory size of character */ + p_font->i_memory[ i_char ] = i_total_length; + + } + + close(i_file); + + return p_font; +} + +/***************************************************************************** + * subtitler_UnloadFont: unload a run-length encoded font file from memory + *****************************************************************************/ +void subtitler_UnloadFont( vout_thread_t * p_vout, subtitler_font_t * p_font ) +{ + int i_char; + int i_line; + + msg_Dbg( p_vout, "unloading font" ); + + if( p_font == NULL ) + { + return; + } + + for( i_char = 0; i_char < 256; i_char ++ ) + { + if( p_font->p_offset[ i_char ] != NULL ) + { + for( i_line = 0; i_line < p_font->i_height; i_line++ ) + { + if( p_font->p_offset[ i_char ][ i_line ] != NULL ) + { + free( p_font->p_offset[ i_char ][ i_line ] ); + } + } + free( p_font->p_offset[ i_char ] ); + } + if( p_font->p_length[ i_char ] != NULL ) + { + free( p_font->p_length[ i_char ] ); + } + } + + free( p_font ); +} + +/***************************************************************************** + * subtitler_PlotSubtitle: create a subpicture containing the subtitle + *****************************************************************************/ +void subtitler_PlotSubtitle ( vout_thread_t *p_vout , char *psz_subtitle, + subtitler_font_t *p_font, mtime_t i_start, + mtime_t i_stop ) +{ + subpicture_t * p_spu; + + int i_x; + int i_width; + int i_lines; + int i_longest_width; + int i_total_length; + int i_char; + + uint16_t * p_data; + + char * p_line_start; + char * p_word_start; + char * p_char; + + subtitler_line_t * p_first_line; + subtitler_line_t * p_previous_line; + subtitler_line_t * p_line; + + if( p_font == NULL ) + { + msg_Err( p_vout, "attempt to use NULL font in subtitle" ); + return; + } + + p_first_line = NULL; + p_previous_line = NULL; + + p_line_start = psz_subtitle; + + while( *p_line_start != 0 ) + { + i_width = 0; + p_word_start = p_line_start; + p_char = p_line_start; + + while( *p_char != '\n' && *p_char != 0 ) + { + i_width += p_font->i_width[ toascii( *p_char ) ]; + + if( i_width > p_vout->output.i_width ) + { + /* If the line has more than one word, break at the end of + the previous one. If the line is one very long word, + display as much as we can of it */ + if( p_word_start != p_line_start ) + { + p_char=p_word_start; + } + break; + } + + if( *p_char == ' ' ) + { + p_word_start = p_char+1; + } + + p_char++; + } + + p_line = malloc(sizeof(subtitler_line_t)); + + if( p_line == NULL ) + { + msg_Err( p_vout, "out of memory" ); + return; + } + + if( p_first_line == NULL ) + { + p_first_line = p_line; + } + + if( p_previous_line != NULL ) + { + p_previous_line->p_next = p_line; + } + + p_previous_line = p_line; + + p_line->p_next = NULL; + + p_line->p_text = malloc(( p_char - p_line_start ) +1 ); + + if( p_line == NULL ) + { + msg_Err( p_vout, "out of memory" ); + return; + } + + /* Copy only the part of the text that is in this line */ + strncpy( p_line->p_text , p_line_start , p_char - p_line_start ); + *( p_line->p_text + ( p_char - p_line_start )) = 0; + + /* If we had to break a line because it was too long, ensure that + no characters are lost */ + if( *p_char != '\n' && *p_char != 0 ) + { + p_char--; + } + + p_line_start = p_char; + if( *p_line_start != 0 ) + { + p_line_start ++; + } + } + + i_lines = 0; + i_longest_width = 0; + i_total_length = 0; + p_line = p_first_line; + + /* Find the width of the longest line, count the total number of lines, + and calculate the amount of memory we need to allocate for the RLE + data */ + while( p_line != NULL ) + { + i_lines++; + i_width = 0; + for( i_x = 0; i_x < strlen( p_line->p_text ); i_x++ ) + { + i_char = toascii(*(( p_line->p_text )+ i_x )); + i_width += p_font->i_width[ i_char ]; + i_total_length += p_font->i_memory[ i_char ]; + } + if(i_width > i_longest_width) + { + i_longest_width = i_width; + } + p_line = p_line->p_next; + } + + /* Allow space for the padding bytes at either edge */ + i_total_length += p_font->i_height * 2 * i_lines; + + /* Allocate the subpicture internal data. */ + p_spu = vout_CreateSubPicture( p_vout, MEMORY_SUBPICTURE ); + if( p_spu == NULL ) + { + return; + } + + /* Rationale for the "p_spudec->i_rle_size * 4": we are going to + * expand the RLE stuff so that we won't need to read nibbles later + * on. This will speed things up a lot. Plus, we'll only need to do + * this stupid interlacing stuff once. */ + p_spu->p_sys = malloc( sizeof( subpicture_sys_t ) + + i_total_length * sizeof(uint16_t) ); + if( p_spu->p_sys == NULL ) + { + vout_DestroySubPicture( p_vout, p_spu ); + return; + } + + /* Fill the p_spu structure */ + p_spu->pf_render = E_(RenderSPU); + p_spu->pf_destroy = DestroySPU; + p_spu->p_sys->p_data = (uint8_t *)p_spu->p_sys + sizeof(subpicture_sys_t); + + p_spu->i_start = i_start; + p_spu->i_stop = i_stop; + + p_spu->b_ephemer = i_stop ? VLC_FALSE : VLC_TRUE; + + /* FIXME: Do we need these two? */ + p_spu->p_sys->pi_offset[0] = 0; + p_spu->p_sys->pi_offset[1] = 0; + + p_spu->p_sys->b_palette = 1; + + /* Colour 0 is transparent */ + p_spu->p_sys->pi_yuv[0][0] = 0xff; + p_spu->p_sys->pi_yuv[0][1] = 0x80; + p_spu->p_sys->pi_yuv[0][2] = 0x80; + p_spu->p_sys->pi_yuv[0][3] = 0x80; + p_spu->p_sys->pi_alpha[0] = 0x0; + + /* Colour 1 is grey */ + p_spu->p_sys->pi_yuv[1][0] = 0x80; + p_spu->p_sys->pi_yuv[1][1] = 0x80; + p_spu->p_sys->pi_yuv[1][2] = 0x80; + p_spu->p_sys->pi_yuv[1][3] = 0x80; + p_spu->p_sys->pi_alpha[1] = 0xf; + + /* Colour 2 is white */ + p_spu->p_sys->pi_yuv[2][0] = 0xff; + p_spu->p_sys->pi_yuv[2][1] = 0xff; + p_spu->p_sys->pi_yuv[2][2] = 0xff; + p_spu->p_sys->pi_yuv[2][3] = 0xff; + p_spu->p_sys->pi_alpha[2] = 0xf; + + /* Colour 3 is black */ + p_spu->p_sys->pi_yuv[3][0] = 0x00; + p_spu->p_sys->pi_yuv[3][1] = 0x00; + p_spu->p_sys->pi_yuv[3][2] = 0x00; + p_spu->p_sys->pi_yuv[3][3] = 0x00; + p_spu->p_sys->pi_alpha[3] = 0xf; + + p_spu->p_sys->b_crop = VLC_FALSE; + + p_spu->i_x = (p_vout->output.i_width - i_longest_width) / 2; + p_spu->i_y = p_vout->output.i_height - (p_font->i_height * i_lines); + p_spu->i_width = i_longest_width; + p_spu->i_height = p_font->i_height*i_lines; + + p_data = (uint16_t *)(p_spu->p_sys->p_data); + + p_line = p_first_line; + while( p_line != NULL ) + { + p_data = PlotSubtitleLine( p_line->p_text, + p_font, i_longest_width, p_data ); + p_previous_line = p_line; + p_line = p_line->p_next; + free( p_previous_line->p_text ); + free( p_previous_line ); + } + + /* SPU is finished - we can ask the video output to display it */ + vout_DisplaySubPicture( p_vout, p_spu ); + +} + +/***************************************************************************** + * PlotSubtitleLine: plot a single line of a subtitle + *****************************************************************************/ +static uint16_t * PlotSubtitleLine ( char *psz_line, subtitler_font_t *p_font, + int i_total_width, uint16_t *p_data ) +{ + int i_x; + int i_y; + int i_length; + int i_line_width; + int i_char; + + uint16_t * p_rle; + + i_line_width = 0; + for( i_x = 0; i_x< strlen( psz_line ); i_x++ ) + { + i_line_width += p_font->i_width[ toascii( *( psz_line+i_x ) ) ]; } + + for( i_y = 0; i_y < p_font->i_height; i_y++ ) + { + /* Pad line to fit box */ + if( i_line_width < i_total_width ) + { + *p_data++ = ((( i_total_width - i_line_width)/2) << 2 ); + } + + for(i_x = 0; i_x < strlen(psz_line); i_x ++) + { + i_char = toascii( *(psz_line + i_x) ); + + if( p_font->i_width[ i_char ] != 0 ) + { + p_rle = p_font->p_offset[ i_char ][ i_y ]; + i_length = p_font->p_length[ i_char ][ i_y ]; + + if(p_rle != NULL ) + { + memcpy(p_data, p_rle, i_length * sizeof(uint16_t) ); + p_data+=i_length; + } + } + } + + /* Pad line to fit box */ + if( i_line_width < i_total_width ) + { + *p_data++ = ((( i_total_width - i_line_width) + - (( i_total_width - i_line_width)/2)) << 2 ); + } + } + + return p_data; +} + +/***************************************************************************** + * DestroySPU: subpicture destructor + *****************************************************************************/ +static void DestroySPU( subpicture_t *p_spu ) +{ + free( p_spu->p_sys ); +} diff --git a/modules/codec/spudec/text.c b/modules/codec/spudec/text.c new file mode 100644 index 0000000000..be7fd00a26 --- /dev/null +++ b/modules/codec/spudec/text.c @@ -0,0 +1,85 @@ +/***************************************************************************** + * text.c: text subtitles parser + ***************************************************************************** + * Copyright (C) 2000-2001 VideoLAN + * $Id: text.c,v 1.1 2002/11/06 21:48:24 gbazin Exp $ + * + * Authors: Gildas Bazin + * + * This program 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 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#include /* malloc(), free() */ +#include /* memcpy(), memset() */ + +#include +#include +#include + +#include "spudec.h" + +/***************************************************************************** + * Local prototypes. + *****************************************************************************/ + +/***************************************************************************** + * ParseText: parse an text subtitle packet and send it to the video output + *****************************************************************************/ +void E_(ParseText)( spudec_thread_t *p_spudec, subtitler_font_t *p_font ) +{ + char * psz_subtitle; + mtime_t i_pts; + + /* We cannot display a subpicture with no date */ + i_pts = p_spudec->bit_stream.p_pes->i_pts; + if( i_pts == 0 ) + { + /* Dump the packet */ + NextDataPacket( p_spudec->p_fifo, &p_spudec->bit_stream ); + msg_Warn( p_spudec->p_fifo, "subtitle without a date" ); + return; + } + else + + /* Check validity of packet data */ + if( (p_spudec->bit_stream.p_data->p_payload_end + - p_spudec->bit_stream.p_data->p_payload_start) <= 0 + || (strlen(p_spudec->bit_stream.p_data->p_payload_start) + > p_spudec->bit_stream.p_data->p_payload_end + - p_spudec->bit_stream.p_data->p_payload_start) ) + { + /* Dump the packet */ + NextDataPacket( p_spudec->p_fifo, &p_spudec->bit_stream ); + msg_Warn( p_spudec->p_fifo, "invalid subtitle" ); + return; + } + psz_subtitle = p_spudec->bit_stream.p_data->p_payload_start; + + if( psz_subtitle[0] != '\0' ) + { + subtitler_PlotSubtitle( p_spudec->p_vout, + psz_subtitle, p_font, + i_pts, + 0 ); + } + + /* Prepare for next time. No need to check that + * p_spudec->bit_stream->p_data is valid since we check later on + * for b_die and b_error */ + NextDataPacket( p_spudec->p_fifo, &p_spudec->bit_stream ); +} diff --git a/modules/demux/ogg.c b/modules/demux/ogg.c index e9b8a342bb..0f44fc2609 100644 --- a/modules/demux/ogg.c +++ b/modules/demux/ogg.c @@ -2,7 +2,7 @@ * ogg.c : ogg stream input module for vlc ***************************************************************************** * Copyright (C) 2001 VideoLAN - * $Id: ogg.c,v 1.7 2002/11/05 21:57:41 gbazin Exp $ + * $Id: ogg.c,v 1.8 2002/11/06 21:48:23 gbazin Exp $ * * Authors: Gildas Bazin * @@ -87,6 +87,7 @@ struct demux_sys_t /* current audio and video es */ logical_stream_t *p_stream_video; logical_stream_t *p_stream_audio; + logical_stream_t *p_stream_spu; /* stream we use as a time reference for demux reading speed */ logical_stream_t *p_stream_timeref; @@ -168,7 +169,6 @@ static int Demux ( input_thread_t * ); /* Stream managment */ static int Ogg_StreamStart ( input_thread_t *, demux_sys_t *, int ); -static int Ogg_StreamSeek ( input_thread_t *, demux_sys_t *, int, mtime_t ); static void Ogg_StreamStop ( input_thread_t *, demux_sys_t *, int ); /* Bitstream manipulation */ @@ -227,8 +227,6 @@ static int Ogg_StreamStart( input_thread_t *p_input, } } - //Ogg_StreamSeek( p_input, p_ogg, i_stream, p_ogg->i_time ); - return( p_stream->i_activated ); #undef p_stream } @@ -256,17 +254,6 @@ static void Ogg_StreamStop( input_thread_t *p_input, #undef p_stream } -static int Ogg_StreamSeek( input_thread_t *p_input, demux_sys_t *p_ogg, - int i_stream, mtime_t i_date ) -{ -#define p_stream p_ogg->pp_stream[i_stream] - - /* FIXME: todo */ - - return 1; -#undef p_stream -} - /**************************************************************************** * Ogg_Check: Check we are dealing with an ogg stream. ****************************************************************************/ @@ -383,9 +370,20 @@ static void Ogg_DecodePacket( input_thread_t *p_input, } /* Convert the pcr into a pts */ - p_pes->i_pts = ( p_stream->i_pcr < 0 ) ? 0 : - input_ClockGetTS( p_input, p_input->stream.p_selected_program, - p_stream->i_pcr ); + if( p_stream->i_cat != SPU_ES ) + { + p_pes->i_pts = ( p_stream->i_pcr < 0 ) ? 0 : + input_ClockGetTS( p_input, p_input->stream.p_selected_program, + p_stream->i_pcr ); + } + else + { + /* Of course subtitles had to be different! */ + p_pes->i_pts = ( p_oggpacket->granulepos < 0 ) ? 0 : + input_ClockGetTS( p_input, p_input->stream.p_selected_program, + p_oggpacket->granulepos * 90000 / + p_stream->i_rate ); + } /* Convert the next granule into a pcr */ if( p_oggpacket->granulepos < 0 ) @@ -435,6 +433,7 @@ static void Ogg_DecodePacket( input_thread_t *p_input, p_oggpacket->bytes - i_header_len ); p_data->p_payload_end = p_data->p_payload_start + p_pes->i_pes_size; + p_data->b_discard_payload = 0; input_DecodePES( p_stream->p_es->p_decoder_fifo, p_pes ); } @@ -649,10 +648,14 @@ static int Ogg_FindLogicalStreams( input_thread_t *p_input, demux_sys_t *p_ogg) /* Check for text (subtitles) header */ else if( !strncmp(st->streamtype, "text", 4) ) { + /* We need to get rid of the header packet */ + ogg_stream_packetout( &p_stream->os, &oggpacket ); + msg_Dbg( p_input, "found text subtitles header" ); p_stream->i_cat = SPU_ES; p_stream->i_fourcc = VLC_FOURCC( 's', 'u', 'b', 't' ); + p_stream->i_rate = 1000; /* granulepos is in milisec */ } else { @@ -772,7 +775,7 @@ static int Activate( vlc_object_t * p_this ) p_ogg->i_streams + 1, 0 ); p_input->stream.i_mux_rate += (p_stream->i_bitrate / ( 8 * 50 )); vlc_mutex_unlock( &p_input->stream.stream_lock ); - p_stream->p_es->i_stream_id = i_stream; + p_stream->p_es->i_stream_id = p_stream->p_es->i_id = i_stream; p_stream->p_es->i_fourcc = p_stream->i_fourcc; p_stream->p_es->i_cat = p_stream->i_cat; p_stream->p_es->p_demux_data = p_stream->p_bih ? @@ -786,7 +789,6 @@ static int Activate( vlc_object_t * p_this ) switch( p_stream->p_es->i_cat ) { case( VIDEO_ES ): - if( (p_ogg->p_stream_video == NULL) ) { p_ogg->p_stream_video = p_stream; @@ -798,9 +800,33 @@ static int Activate( vlc_object_t * p_this ) case( AUDIO_ES ): if( (p_ogg->p_stream_audio == NULL) ) { - p_ogg->p_stream_audio = p_stream; - p_ogg->p_stream_timeref = p_stream; - Ogg_StreamStart( p_input, p_ogg, i_stream ); + int i_audio = config_GetInt( p_input, "audio-channel" ); + if( i_audio == i_stream || i_audio <= 0 || + i_audio >= p_ogg->i_streams || + p_ogg->pp_stream[i_audio]->p_es->i_cat != AUDIO_ES ) + { + p_ogg->p_stream_audio = p_stream; + p_ogg->p_stream_timeref = p_stream; + Ogg_StreamStart( p_input, p_ogg, i_stream ); + } + } + break; + + case( SPU_ES ): + if( (p_ogg->p_stream_spu == NULL) ) + { + /* for spu, default is none */ + int i_spu = config_GetInt( p_input, "spu-channel" ); + if( i_spu < 0 || i_spu >= p_ogg->i_streams || + p_ogg->pp_stream[i_spu]->p_es->i_cat != SPU_ES ) + { + break; + } + else if( i_spu == i_stream ) + { + p_ogg->p_stream_spu = p_stream; + Ogg_StreamStart( p_input, p_ogg, i_stream ); + } } break; diff --git a/share/font-eutopiabold36.rle b/share/font-eutopiabold36.rle new file mode 100644 index 0000000000000000000000000000000000000000..2f0cca6b229331031968dc856faac2c7700d8b81 GIT binary patch literal 45242 zcmeI52X|ySlAY6$EV5XvYBxQrmChNhW@T31t-M)z@4ffld;iy^``sG>;AJLRRo&C- zJ-u^IlKJ4nBM<;y{_M@q{NUAo_icWD>HU7k=eRo@Z}hj@AMf;cJnV0dhnwSmzdzjU z_eZ6^?xXrthkVqhdi&~+g70upcf0+~d<1HG7MPTP$nEZMIvzEc62PxU8Frv@Qt2(| zjfXQls|nqB!0Pt6JF3%TNO$%BPd)8-e4y#cwA};M8_++Qy8C;R^u+K*k^SAwg7{Zz zyh@Eh#Hyp*ne__ttQmJM6KRBlKKC=(1XB~!N6>_VsRJ=b)~zWlbm(Tm>qf*l>_np7 zAvk~i|G(^e``!H9NIXv(M0C8_y%Jkt?fzl6pat+?JTfQPyZ!6k?FM7ld^Y=`YcE}> z-{bMdLx`~X1_J3!C+qtZD(w&N1kQewcxC#X)S8~eUfM%l;OTB}?p4d39;vBkjdMB5 z?tmb;AJvjT?e@3I$gt;$hs#lR!e|ca!8OwHUJxBTYz9BG(Y&LNiYQA{`$7aJqb0QW~VFc<&~l6&xnPLU+ON7186VSKAqXeLr_QLOBc(!DOD zxV>5+P(=ftg^E<&7^wwgs@RP3+@+euPPi-kZV=7vGf@c54EXNu#&Ux#yDLd5xsrsP zeSZ1}`yHR-?n_MqlGa-Z-fIbvr0sZokT_~cb##kDouuop3y8z+CL#v4529$ur)-*l z+3D~mO&?9nrr>FRM^*ZiFlt@{@#8>FFh8lPvQ)=K^_@0X3PmuJ<*<8o*%FF}D)*O# zDk)Iz1>+piHY}|k-VES1;{dih6vDgH(VK0FPBh>oNJ>I9I7H@kRbuIXpF zX`%<#$nAK%Q)f5W#~VG$CTpU=(yHW9-#1Z)tN(xbPwsd1!9w5c-W=8I!|t`3Su>8i z2P`zgk47AJ_v+h8bioYWy?Q;Mp7={}nLxo7L{2fBySoq1l)SmQcrw0aG0wB;UGv&w z)fRCwpd0DmV*zJ-x-iI219*j;8Vl!_-eKJxgEjzkLU)IQWK-EZ2dx8Bk7eKHxy9$O z)gpXYIt>%|(b=F?X1%);9;z4Qo=Wp?~=Cf^w6y6kdOnJmNP;QLJXyACX)A)#xm#>=PGI z0qraQ|CO)zJ3dTGk`PT9$6LrFzJzGd5djbdLsVt73z`xy?O+Ji1ZAc_s3J)WN&;W1 zU1nX6aWPV5n5XF$o zgp8Gf|DisX%<@>A34eao<9^2nDUdaj1G2l7By05HvB4O_K7ksnDH!9I1`aW&lLaMS zMgU_BtIX)Cfp*m!Ol$D=W5~8V-rDwtH0lQwzmde1AvKb8Y67?ce3Ljs_ktMc^dQx3 zB_Qzu@j&)T9N_}X)YC!KzJWg{tVQuF4?pv6zvBaM@d8y(-W?-)*{e2t77evV69zIf zO#NtFfuPLe^o+4PV9K{XM;u^(lbdX1WUX&bxgSZGXIxQ;anDCYSLMj7(s9HH+zy-n z+z4^8Rm}kOH$cRf-e4f9TCw!f8yY}AePnx}Ao713f_>%xzxv#{KJ&G)cPIUM0w^b4 zVhU4M75~_z=7!>o@VxQb29mtOmc0^hw7y1O_J>cFhox|70-=`9z8+i~oC@h8!5LxB zlE3C*zx!Gr7)zK%vaT6~#dt*)khV4=780)qGhduPcErPixJj_2uURisE;k1 zV%X|y5k-B=Zbow@erUDCZ({vxH@bYL+Rr8FPSKG4@e#`*-$s15A2uJVfGSmaYc-zh zdd>fJ6U-Il4}X^7627N}KUfI?6#kevVTwJ!Ov?CS<^A|EGA$ynH7Bd9^Ad#@VEgqI z-^8ic94KcTZv=NwBWT7m7vZmBw3Q(8uy7XxBA#b*+9WW}l(a;UOf`(soT5DQRZ^6Z zBqfn{N@kL@lu>#$naVP-vEZrvTr!mv;~S#9JE8r`F91jUPlFL`N?F<|UNb9ROQhQE zME7=eZ|p@N0(U5=3?lQF!ehmW%ZK0a#eP>GEC>rWky7zZCS9Tuavwt#uLH2`?T=@9 zh<;T_EDR8J3eOQKL)OT)%yp!!5=Ph037~ywwkA|BB*b8MA%bL@(?juTlOKZ;=b$obqKpIGQl7F<66rmc40##w#6y9u*#&5F1zue}tFpa2SP zCEBgMk*L_2q$Qd~W<%>hL^E-=c`iHgjJDyit#qfL2nohs82w5KTn_}nQge?fFLUl@ zSpEPNsZugQwUo-bGxuPn+;x1gPRhzjZB@#^5@h$Tp-6hz-5v-SJvC$=UzM(rL4;j3 zf;AkvM=d+!uzyrbPqXykjh{?J@0e!sA<0`G5692dn?9eiXEqsm`TU!wcRZXyLL$<_ zgh|Y0lT;xVa6j}OjGXnh%9EV}rzmW6p`QLyFA*H@TMX~j%nTulO;ZTH5_qWISiuo# zHG>kSX*{H(E3Q)=}Xgt6|a;AP^+D!3=mcn6(o3IcA zF~pMEGcSHk{+7+g*DL2BI8M9hv`x<&eyv>luLt z`n-@O*C*gqr}S$P;OBu6c{>JvlL07>4U(p`y0N(do0ilofLwd2~Z6UA94o~#&{Msx^rI9dQ4-#nU=k&N0 zM4t)f@l?~c%PGvJ9dVaXE7=<1FC_pI=U+x5t*~eRKBGPa%Wr0$+AUs>CrSM zh5}~-S)sbgh7}r+LsZR z)moomFaL?y2UDul_9%Wj_8}*HRoAoUpPgnsJ0A)r-9qf%`LN|?qR9Mg%N;6d30i{g zGXhWwd=WbvPxkJ~1TixCt{sHbY`bhKWh2pbga+;Ohl!6gP@qkTnb27WaWU+qj#dTg zN6dm?#d;qO+I6eC4F81K1`r99K=iL(u4>gvePD z$dZh%x4P5Qx@b+`)G}depdunP0wfb?b%&H;vLomw91^0~p>mbg3#W3e7N-m0#!^c0 z<&<4c=_BX2ooT(*U0okPLn`Do#76B8Z=pdnrXJyk9gUEEFBim2C3*1fr;}D=TQ(;_ zL~>u|e`E=zO_rp8s5?aO$t4YxjJnudFcWzc6(Uqx^0vjTWCRN;yA(9C8{rUMuu7>y zctPB(Ai(!hvLnRE#ohCX=2M1H{E$BNC#c0Ewv?omCt^q&Zmh}QzG||WN>lq_tfM!c z_VB@4{D#1+N(-0lfUlXzn=udZ!Amsj%-GtQsi)_&MC{qy(#y!~LkJe;9wKs%zSf^0 zkSu@+8_S$SQF{?3x z*!mNKHS2=dp!I6tN@gq`CTL6IhM@1u%!C>?O(|@S1Q0V=vqJPAc?O`7;SFBVQLc5z z!Emco-odt8^-Oh9xa}Kgc^|4hFePHpT!Ow>W>+x@TVtJs7f5F!D1uEvoHB8RbP@WH zc<2o3OV-eX;Sm5cjFd-kioL&;IeEz=_?_>G0OMMCeQ+%RU$#Ny5)IFzx|`f#GDHWF zUw_$>X1yv}LQjk!fRBNo>9-U~{#|tf`RXy+mro{p>pQsHwh*{qNaMVvIP^e~yhV>t ziURm{2%!Fw%Fc((P$KK9js$G_v8_Tx?$dXv9bqDr<>6~HHIVwy=b!Jqpikb&Y)*yv*kumm26r|0}$Wg#p< z77+d^Jcd>j!p-pdOrl9XL)FN_haHL6tL$QSMD6;QmVBOpBpIwgNS$;9ldE4Bt0$m4>& z1V97nbJ7lx*Qm)+=kdZx$`p0Hp0Si{$D4KHHgQ(*tQ!`=L<;bvn=wQlG=CPqE+2mX zFWK+-5D9yQ<9@FIi#D?`44e@Vs2C?sHe)yC@t zM~;prds%!RjtajdyCd7wy^%9E>@{az!*KY>PDtDJy#m2%uC!RpH;+jN1fAFe%!rB! z?VBr^2-Lc3>mis_Ytrg#R%i$)(4S@Ptimkl0aZa~Lu$lsrIn5G-M@7a@Ke9PJ+jhE zaavB17tlwnr8;ix`EFcz*5C0U%UaOR@3>v50f#WZOC_kNbma{;&a=wt99^RjX{eAE zK;j5#3!vRr-vf}A`051>)kyY%dOp8R7O4C1JmO^)&~p38ST!q>{M{sq|s; zCzhKJ>QP6s~RcEI09F zYOA3xlp(yVL$yCR1?KYk5B$V_S0CodPj|P6;}<=*KB+rdd!lrVN&MQgL7;>J$Z{x= zppgmL^STUXCLjn>PxSO7&jyYX)IQgp4b%c{W&&^K&N0IgF&C1(vc6f+Y@j{m4m2Al z@g}%m;^XDTnmuJkd#T;QDoP)jJuz}8mXn1812i4GDJb~{EXQOCSvNOpW$`3KB^Tmw z{GQ}TlJFS^_NQ=MKL5e*?{|E#K8TvMUfU6|Xg=*jc%FIIwvMZD*h6_tmL^PP&783( z^8F;@%Iq`l)X+l02%vaXP!mj<2p41-AO0++=b7xqRkKOds2!CSutG*AUrJkItxoVk zXU3Bjb0MUzZC-s;Z&ksK&pN*{{5&WxvmI;F5ioOXwWrS@)!7m)8zJV*g=ElXL(3sQ zD!}@g@>h8jE*=iPBern2_+8wOC|v#jhodX>SCtoxcre#vvI4z7oA7)3= zAV6iRZLkHp%*wcMp+pRPDe9_cWtVC3O!gp15l}4bQwR*GSxJ-Dg<5BxV!)05e!JN# zDg){@Gt^MgGqiXn`&mjS+!nsHbD$lxwmjh=%&M8B`OI3tnibolZ8gb7$;jnQwdVZc zFZa9pQ2+DTEnKa`XE50t9AX#Sxk}!T`2oX-to!&w7O2RQCw8MY}mYB z95}pMQDVzNlv;E)E1TuG6F3W^9fH7e=mupIIS-$U7Lo_Y zO2ps6@2d}gWZipV?$WSd(935;U+MYIJ;-!6HNh z<{3mmyCviPEYUR6N<0@Spr{rxO=}_R>nsQDD&tW;X$ljeA6d9hR$V^-(O> zHPPFI8CXL*N`X&Nvr(_ew_b(jYm299q5SPvC>y^Uh6Y&Xl3o*zBAbiFw@|BWjMIi2 zjxY)NTc9VToBu(&fV+n=5NpNi5)SIeiM}3BoJ1qp|t#xXrwF zNN7w{ghW$>>X}`**b#AZJe}Lm;(sRIO32>*NVBK@-*>-PvZBzRTdIdKczB_~l|Ig9 zfo}Y*WM8NLbzb3uH&<&dea82ahDAOu<1lN^AD^e|W9LLyRtFl#t8yM|BR(c>*E=B+*Qz2xt-!OC=+;nICHxp%(#i?-K)6 zo!TK|*teE`ux1>EuJev-Kko&k>b{#Of4T#Wrj8SUm@Rydt~0v<_L0{ zKeX?qjy=f8XhPn1jmV|K1T!NO^sCMQHpsoeH0SrT%rsq4((vV5+)O~BO4z$(LgI1_ zW=$xPOs>k6=CGI{Q<1nI%}xFlf0_S z%l#wgPi@zTZ@6Ur7Jn6TScc-S5(^WkB!J&~uWG$qxizK(l2`+Obe+RHkwX(n#=Iu43@9mm!OYp{=4$^Ge{+VARv z=D*hNhDAy2J3hv-M(^#4#{wieE>$mV*|QYuUYnbdMu-r}JaN_`TV2^ zHx(w>t46u2hrCyYHGqUuBVJUPeYDnu8E6s1X!7}D4r2mUwt>{pYuSo1eKr4{EsmKGoR_U<=Njy?7)Xb3baBH> zAgk=oWf8~&wR#OY6XKRMI)0(pP+w~u>7w&N#CtB!L-Z^tLfQ?57leY)|c)*Bu6P3ZxncJX+Yaz6hoa!{!1oP zzp{y^+!9D-c43=a&h2m+u0H(P@nR|>3x0i|&y)1d{n*pzx}SjP7gKG{lfaSK|JGfp zMTb5BO|%&(6-9&Lf~`;bR;^IfRq7zP(H1@JR|)+|q!5|9+Fwq29D@uAsWf_YV5ys( zDrHF5=}ex}k1{Kh`Rx8B_^#se42o;^LZz0|D^!iTgbO`MpT!Wz^m2u}9g`~l&odp~ z6XmsPj!9{mGi~VE^Pj7|RBxxU3$MM9&wF>|jfH!&3y*}?4hvxGuZ5@gV#pS%$?lyS zGFRI&HqTNJI+zdJ7GJAimGaC5f{Keslk6Lqyqpl^8w}P?RM6NCPgi(l1{iS?B7|D+ zw`)vqI`Xk5b#P}*>MeC2nbZgcr*4L1^7c>N@Hhc}omc1M#4XIHT6$S;fAd~3pN%E8tl5IdhiX{`brnowmi!{E}U zJI@=dvgp<`DkrnSE-L1O?~UWzQ7#T}n3{t@PcC)X*tQFV9N+kM7qO@;tjk=5B zU*>~nI5;iQjd||aOrqmuiMY=$tuSAG?lE^#%h31rEs_$SJFT>A<(1TXobGgbGD(C> zv9Fw4^a))Z9hSVXFD8jQ>eM(Blg^N9RvmWjFUXGLsFWZOQa=V?d~5OCVmu}t&WHcv86HfE@Z>?%sXC%b4mYa`EyEll>w z&_tRbh*+KkF)TT-GKQ)eEv=9#%QK{5%wLI6N%3OUeObKE;yCij&NWib8W_)7yM)>x zby9;_yO2FAl``UP8Lx83rHes)&4dV*ZPJvty~(ykaKFxujYQO;v#H2n>r4csf{$fj z-4H|+PJ$AUg#<6$6f28sTY%C5w3gD!xjqcc6onD!$d96#5NsWSi`~eE!Sd@m*R^>X|1*kzVJ1 zqZ`BD!jM}hzm~ER1@S)$U~941n577DYAc;;@-r}ou`zQUJx*=sY-vx$@{e&*m$UY` z=pT31{;$jxTcXk0d64B{GCIEYEGJ}QC6vONvb@H_1{|Ff{6;%!?AZ z*)+z$2jr~>dOiO-NS;M_WU$4!gXf`DRdG)lL7=F?=qa!$t0Kx(i9SDdw^S%` zD?ct(!ezZ6C-Tp5R0E|VU~9tFDylxw*RjI%!eA|}8o&c~TC)BL+7a?Gjw!~#=T)jD zz!*$@%L)`cr%zkQ+`xtVxg8n z(ptqryE=psq=eh<_7gfkFIGBqR@L3@W9c12u=4s=Za}v^FX}cBJED`PyKS4vSH&3~ zOIZ>ASWPfh0T)kMN9bf2KNCdK=MJXY4gDJre{G&y$6aIY(UO~K0L!h(RE9w-2gvPB zSi-Q#CmJ(eoJUn^#C+sg)$n<-w!fxv@ z%p&R3)+>+%mY}J-NuZ`x-_eX8jtlEpzcE9@l zoK;Mpo22Q-7a=$E^4T=~k;xh6)p6$p*kW$|{&$FwcXi5(=?jOvuM-DPpLbF~eRLf9 zsv9se|HfPz;WO&|JBwtUhVR$B@oG99+tfeNN%7!lSi&GsiF?Zwh^gK+QUaEc8HE`8 zs$t3Go_);po1%z9T2*K2wIn3jGK`LEth(s%WZALQQiw5(NK*7kGD53mbONR zm@CquU3CE-{ujSBVYRIXr!JOQx2>mfOUIH;Z5+q+tqEf`Ub6diLDfqCQW`mz%TV)q z&0dr<*#>ap+41=FJL)qB%7#sKRR#h7g@zD$%WIN5@h`hmlNv9*EOe(EULJW0a1;HpvJ}i&yIJZ2j z^=L(JsF3VO2?d5*aAkiUF_R!_P{~MVWmnr-bCG-NI-*8|szTv$UkQH0CLQa@y5fwbf%)i@)>+~Wvjz^4Zy0y@)S$`%qJIFqN3be zJzkEsfV*2H;=rS>-sr!SR&<;eyywKwuUxOU^vl8)b$-*zEN6CAW~M)7G*_a%M8Avt zensHaTn!#YwN-VU(%gB)HD_IWi+a^t9h4#bO#+CwyvNOPsQmcF5pkj1m}#eNNoA?t2^KjcCn)6NqR96Q&}cTZGas1 z2+H-im)f>D`?&1%Opp5OE-r^FFPO2BvzTmUmiJJ{p(MRP$kfa0E$iN_QZh$g-ehvu z3O4>EZ^MwnB=Mx4MK#Hb`gbBybI31!pr}G}`;!Y6RE>uyn~n98Ih>lq7WDp^%C_g= zqC(D|6dlm5K?#|d*+$!IZET@sd(m;NJp}Tedq^Xy9&2|tF()ExutJr# z?HFcSKz`AMZ5xJ2-tnJ367cH$77zyl{87_O@j|?~R9g3SJ;{X4b!~%hI&O_SBBi%AyvG+cEsy*;n zdvT!%ef8mARGI@riCXO7u!r(~sl!8*Ig~Gh z2;Kb{>Z)H^Y~$u3My{yexj(NSH^`el7!0vwH4B?W3u{>tWHHl^OpJKwnMhU`w~zN| z%gzzKNB($k&f~v~1%_dI!3Lp_H>MdxSqIou2_&(c|l|R*7Nwy0r5=78BH3Hrm?MP z^fk4FHI_4P%V0{jp5^qR&*KvnmwWrNHT_qKzG6yk;>3WSm3cW;4KDd2(a5q2SE|IY z#pB0G$=9T{|-^L=uOxaxC* z&?EUMO?}HL%&~bc?bxoWAoH5u#lu2Q!iDZ2iv5imbG*fw76{9!D#TFaM5`0DCeuny zJ0B#hdZ_*oCO_LX`f-)#=mN<@;%lUn?ckjPRDkTr8sG9r@=gK2Nk<&@%&!idrl{V# zqi?UdM*8?eG#p14CtBYxOW(hF$$678Vy269dTP=^M^+X$k^$j0w5piY=nhbw4`RZhhI5suAFB)RRW4AXL<}c zg7+C1ucFw9VLsDM_IsS{-z}@~dH;@yC-nTniFX_Po0Y`eUV2gIzk4<&nh+E#>V6Hi{-fM}ebN1py4R=ZkFVYqtC(pmp z?5l9BnFaIe)`FW^n0lEp+wZJbwJv*8@wIcmbiB9KyHe8&*AF4jgkq{Q;dff~Y@zE4 zy(;C!SCUC=Khev&$DZh=PeW;UW2X!4Pr1*l>Lq(h-JvCg*R83{w;O!b@&Jzd+7InO2UvH_0-kRwz72Wy-WC+ zty+WdGnweOeT5{-TaYNJL?qx+rDC-|&y zkQEFl_<6(95PDIhl`GQ&=Ws{Rm`!mD_oN|&6J9wRf-%*5=2?%DPeLV3Ly0jYLs~RT zzh*+3vBugf?3>$U9OV=Y5>|fY%>h$C^Y{^ zAF}Y&4n2qPIMbnR^tuyCyoxPf{Z{1*VXOc09!~_Ob0>h#e2vG4S-4&!W@}VIsZF-T zIkL{sG(ue!psa2KJA<0gNcOyPQv$H4_jv13W)oV+49J*=4s-^+D!VT&(3J;9b2~HB zTGgEO&BtvWnuAd4>M8`*h$(Ez>^l^Uu0jnoPUglBQ#>?wdL$DJ4*ug*FJo~u zUJSyEbAt1~T=$S0p!;`c2Pw{y|Mds^-Af;=8VDd2;vK(nm7lA9fNA{f2%$;wFwqK( z36uj8zU?RQZX0F}f$jpnm2=eVJq524&4$URkVNksgwdl|okFkBRdKE`aUH9F`fc9a z9$)~D8tmlPNYGZ^txZoyWC<|*Ga;G1Di?_m4FbP84xIPv~`uxHX$ z;r4mA6bIuXYe~xxHYnVbmJqF%y`UAeQ#LD2#+np9D=@Cj4z(DUMb)>Gp>&@8>yC+I z`l=`Q3UtTwWjXq9A7mf=@)lpcf>PK4orKA35HH=zuzhtAl04=AcxxsRcf z4F|~9q}J!yp}q1TQ6GIzh;^u6=yY}?YN%gIM>4uPeh<$SckjinFd8^Q@4-do7`*g{ zzivqfG>{=%IOg}o$W#V$)BCu-i6ks+#^a$UV%+h=I$aqK>0r2&zKt++R}b4vdWI?1+x6Zrndv~BiU!falHGP3hNPyrA4?5TmDP7J zNNPHJPe6nYdNY|Q z=>?%>19F)p_HfLik)?Qmn*cp38`5nu+tm4Fd=nt`SXfG98XtU6OH4|KEl3iKwQe;L zivSvPh??18>Tt$7bR7*%-RXjwdZ9Cb2i(H4IVL4hDPD=j7C zW1~@FOR?ze%F6#zyg>_EW-+zb{Trk}nEm+SD-%IvY=Z7?|M#jRD&Z}#ilHZLlITI5 zCyih!LEz&|%0@DR^Fc{VOv&d68${y2dUBCBTfaVxOF~K