* Make ParseOption (from input) a global service (var_OptionParse)
[vlc.git] / modules / codec / cmml / cmml.c
1 /*****************************************************************************
2  * cmml.c : CMML annotations/metadata decoder
3  *****************************************************************************
4  * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
5  *                         Organisation (CSIRO) Australia
6  * Copyright (C) 2004 the VideoLAN team
7  *
8  * $Id$
9  *
10  * Author: Andre Pang <Andre.Pang@csiro.au>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #include <vlc/vlc.h>
31 #include <vlc/decoder.h>
32 #include <vlc/intf.h>
33
34 #include <vlc_osd.h>
35
36 #include "charset.h"
37
38 #include "xtag.h"
39
40 #undef  CMML_DEBUG
41
42 /*****************************************************************************
43  * decoder_sys_t : decoder descriptor
44  *****************************************************************************/
45 struct decoder_sys_t
46 {
47     intf_thread_t *     p_intf;
48 };
49
50 /*****************************************************************************
51  * Local prototypes
52  *****************************************************************************/
53 static int           OpenDecoder   ( vlc_object_t * );
54 static void          CloseDecoder  ( vlc_object_t * );
55
56 static subpicture_t *DecodeBlock   ( decoder_t *, block_t ** );
57
58 static void          ParseText     ( decoder_t *, block_t * );
59
60 /*****************************************************************************
61  * Exported prototypes
62  *****************************************************************************/
63 int  E_(OpenIntf)  ( vlc_object_t * );
64 void E_(CloseIntf) ( vlc_object_t * );
65
66 /*****************************************************************************
67  * Module descriptor.
68  *****************************************************************************/
69 vlc_module_begin();
70     set_description( _("CMML annotations decoder") );
71     set_capability( "decoder", 50 );
72     set_callbacks( OpenDecoder, CloseDecoder );
73     add_shortcut( "cmml" );
74
75     add_submodule();
76         set_capability( "interface", 0 );
77         set_callbacks( E_(OpenIntf), E_(CloseIntf) );
78 vlc_module_end();
79
80 /*****************************************************************************
81  * OpenDecoder: probe the decoder and return score
82  *****************************************************************************
83  * Tries to launch a decoder and return score so that the interface is able
84  * to chose.
85  *****************************************************************************/
86 static int OpenDecoder( vlc_object_t *p_this )
87 {
88     decoder_t *p_dec = (decoder_t*)p_this;
89     input_thread_t * p_input;
90     decoder_sys_t *p_sys;
91     vlc_value_t val;
92
93     if( p_dec->fmt_in.i_codec != VLC_FOURCC('c','m','m','l') )
94     {
95         return VLC_EGENERIC;
96     }
97
98     p_dec->pf_decode_sub = DecodeBlock;
99
100 #ifdef CMML_DEBUG
101     msg_Dbg( p_dec, "I am at %p", p_dec );
102 #endif
103
104     /* Allocate the memory needed to store the decoder's structure */
105     if( ( p_dec->p_sys = p_sys =
106           (decoder_sys_t *)malloc(sizeof(decoder_sys_t)) ) == NULL )
107     {
108         msg_Err( p_dec, "out of memory" );
109         return VLC_EGENERIC;
110     }
111
112     /* Let other interested modules know that we're a CMML decoder
113      * We have to set this variable on the input thread, because there's
114      * typically more than one decoder running so we can't find the CMML
115      * decoder succesfully with vlc_object_find.  (Any hints on how to achieve
116      * this would be rather appreciated ;) */
117     p_input = vlc_object_find( p_dec, VLC_OBJECT_INPUT, FIND_ANYWHERE );
118 #ifdef CMML_DEBUG
119     msg_Dbg( p_dec, "p_input is at %p", p_input );
120 #endif
121     val.p_address = p_dec;
122     var_Create( p_input, "has-cmml-decoder",
123                 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
124     if( var_Set( p_input, "has-cmml-decoder", val ) != VLC_SUCCESS )
125     {
126         msg_Dbg( p_dec, "var_Set of has-cmml-decoder failed" );
127     }
128     vlc_object_release( p_input );
129
130     /* initialise the CMML responder interface */
131     p_sys->p_intf = intf_Create( p_dec, "cmml", 0, NULL );
132     p_sys->p_intf->b_block = VLC_FALSE;
133     intf_RunThread( p_sys->p_intf );
134
135     return VLC_SUCCESS;
136 }
137
138 /****************************************************************************
139  * DecodeBlock: the whole thing
140  ****************************************************************************
141  * This function must be fed with complete subtitles units.
142  ****************************************************************************/
143 static subpicture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block )
144 {
145     subpicture_t *p_spu;
146
147     if( !pp_block || *pp_block == NULL )
148     {
149         return NULL;
150     }
151
152     ParseText( p_dec, *pp_block );
153
154     block_Release( *pp_block );
155     *pp_block = NULL;
156
157     /* allocate an empty subpicture to return.  the actual subpicture
158      * displaying is done in the DisplayAnchor function in intf.c (called from
159      * DisplayPendingAnchor, which in turn is called from the main RunIntf
160      * loop). */
161     p_spu = p_dec->pf_spu_buffer_new( p_dec );
162     if( !p_spu )
163     {
164         msg_Dbg( p_dec, "couldn't allocate new subpicture" );
165         return NULL;
166     }
167
168     return p_spu;
169 }
170
171 /*****************************************************************************
172  * CloseDecoder: clean up the decoder
173  *****************************************************************************/
174 static void CloseDecoder( vlc_object_t *p_this )
175 {
176     decoder_t *p_dec = (decoder_t *)p_this;
177     decoder_sys_t *p_sys = p_dec->p_sys;
178     intf_thread_t *p_intf;
179
180     /* Destroy the interface object/thread */
181     p_intf = vlc_object_find( p_dec, VLC_OBJECT_INTF, FIND_CHILD );
182     if( p_intf != NULL )
183     {
184 #ifdef CMML_DEBUG
185         msg_Dbg( p_dec, "CMML decoder is freeing interface thread" );
186 #endif
187         intf_StopThread( p_intf );
188         vlc_object_detach( p_intf );
189         vlc_object_release( p_intf );
190         intf_Destroy( p_intf );
191     }
192
193     p_sys->p_intf = NULL;
194
195     free( p_sys );
196 }
197
198 /*****************************************************************************
199  * ParseText: parse an text subtitle packet and send it to the video output
200  *****************************************************************************/
201 static void ParseText( decoder_t *p_dec, block_t *p_block )
202 {
203     char *psz_subtitle, *psz_cmml, *psz_url;
204     XTag *p_clip_parser, *p_anchor;
205     vlc_value_t val;
206
207     /* We cannot display a subpicture with no date */
208     if( p_block->i_pts == 0 )
209     {
210         msg_Warn( p_dec, "subtitle without a date" );
211         return;
212     }
213
214     /* Check validity of packet data */
215     if( p_block->i_buffer <= 1 ||  p_block->p_buffer[0] == '\0' )
216     {
217         msg_Warn( p_dec, "empty subtitle" );
218         return;
219     }
220
221     /* get anchor text from CMML */
222
223     /* Copy the whole CMML tag into our own buffer:
224        allocate i_buffer bytes + 1 for the terminating \0 */
225     if ( (psz_cmml = malloc( p_block->i_buffer + 1 )) == NULL )
226         return;
227     psz_cmml = memcpy( psz_cmml, p_block->p_buffer, p_block->i_buffer );
228     psz_cmml[p_block->i_buffer] = '\0'; /* terminate the string */
229 #ifdef CMML_DEBUG
230     msg_Dbg( p_dec, "psz_cmml is \"%s\"", psz_cmml );
231 #endif
232     
233     /* Parse the <clip> part of the CMML */
234     p_clip_parser = xtag_new_parse( psz_cmml, p_block->i_buffer );
235     if( !p_clip_parser )
236     {
237         msg_Warn( p_dec, "couldn't initialise <clip> parser" );
238         free( psz_cmml );
239         return;
240     }
241
242     /* Parse the anchor tag and get its contents */
243     p_anchor = xtag_first_child( p_clip_parser, "a" );
244     if( p_anchor != NULL )
245     {
246         psz_subtitle = xtag_get_pcdata( p_anchor );
247     }
248     else
249     {
250         psz_subtitle = strdup( " " );
251     }
252
253 #ifdef CMML_DEBUG
254     msg_Dbg( p_dec, "psz_subtitle is \"%s\"", psz_subtitle );
255 #endif
256
257     /* get URL from the current clip, if one exists */
258     psz_url = xtag_get_attribute( p_anchor, "href" );
259 #ifdef CMML_DEBUG
260     msg_Dbg( p_dec, "psz_url is \"%s\"", psz_url );
261 #endif
262     if( psz_url )
263     {
264         char *psz_tmp = strdup( psz_url );
265         
266         val.p_address = psz_tmp;
267         if( var_Set( p_dec, "psz-current-anchor-url", val ) != VLC_SUCCESS )
268         {
269             (void) var_Create( p_dec, "psz-current-anchor-url",
270                                VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
271             msg_Dbg( p_dec, "creating psz-current-anchor-url" );
272             if( var_Set( p_dec, "psz-current-anchor-url", val ) != VLC_SUCCESS )
273                 msg_Dbg( p_dec, "var_Set of psz-current-anchor-url failed" );
274         }
275     }
276
277     if( psz_subtitle )
278     {
279         char *psz_tmp = strdup( psz_subtitle );
280
281         val.p_address = psz_tmp;
282         if( var_Set( p_dec, "psz-current-anchor-description", val ) != VLC_SUCCESS )
283         {
284             (void) var_Create( p_dec, "psz-current-anchor-description",
285                                VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
286             msg_Dbg( p_dec, "creating psz-current-anchor-description" );
287             if( var_Set( p_dec, "psz-current-anchor-description", val ) != VLC_SUCCESS )
288                 msg_Dbg( p_dec, "var_Set of psz-current-anchor-description failed" );
289         }
290
291     }
292
293     if( psz_subtitle ) free( psz_subtitle );
294     if( psz_cmml ) free( psz_cmml );
295     if( p_anchor ) free( p_anchor );
296     if( p_clip_parser ) free( p_clip_parser );
297     if( psz_url ) free( psz_url );
298 }
299