3 Copyright (C) 2005-2009 Ushodaya Enterprises Limited
4 Authors: Charles Yates <charles.yates@pandora.be>
5 Last Revision: 2009-05-14
10 This document describes how miracle can be customised. The emphasis is on
11 showing simple examples of various aspects of the servers capabilities
12 rather than on focussing on the MLT++ API.
15 THE BASIC CUSTOM SERVER
17 The most basic custom server exposes the entire DVCP protocol and is roughly
18 equivalent to the miracle server iteself, but in this case, it lacks the
19 initialisation from /etc/miracle.conf and the port is hardcoded to 5290:
24 #include <MltMiracle.h>
27 int main( int argc, char **argv )
29 Miracle server( "miracle++", 5290 );
30 if ( server.start( ) )
32 server.execute( "uadd sdl" );
33 server.execute( "play u0" );
34 server.wait_for_shutdown( );
38 cerr << "Failed to start server" << endl;
43 Note that after the server is started, this example submits the hard coded
44 commands specified - further units and property settings can of course be
45 specified via the DVCP protocol.
47 To specify initial DVCP commands from /etc/miracle.conf, it is sufficient to
48 specify an additional argument in the server constructor.
50 The wait_for_shutdown call is not required if the server is integrated in
51 a user interface application.
56 This document focusses on the following areas of customisation:
58 * the Miracle server class
59 * extending the command set
63 * handling pushed documents
67 THE MIRACLE SERVER CLASS
69 The full public interface of the server is as follows:
71 class Miracle : public Properties
74 Miracle( char *name, int port = 5290, char *config = NULL );
76 mlt_properties get_properties( );
79 virtual Response *execute( char *command );
80 virtual Response *received( char *command, char *doc );
81 virtual Response *push( char *command, Service *service );
82 void wait_for_shutdown( );
83 static void log_level( int );
84 Properties *unit( int );
87 The focus of this document is on the 3 virtual methods (execute, received and
88 push). Some further information is provided about the unit properties method
89 and the types of functionality that it provides.
92 EXTENDING THE COMMAND SET
94 The simplest customisation is carried out by overriding the the 'execute'
95 method - the following shows a simple example:
102 #include <MltMiracle.h>
103 #include <MltResponse.h>
110 Custom( char *name = "Custom", int port = 5290, char *config = NULL ) :
111 Miracle( name, port, config )
115 Response *execute( char *command )
117 cerr << "command = " << command << endl;
118 return Miracle::execute( command );
122 int main( int argc, char **argv )
124 Custom server( "miracle++", 5290 );
125 if ( server.start( ) )
127 server.execute( "uadd sdl" );
128 server.execute( "play u0" );
129 server.wait_for_shutdown( );
133 cerr << "Failed to start server" << endl;
138 All this does is output each command and pass control over to the original
141 When you execute this, you will see the following output:
143 (5) Starting server on 5290.
145 (5) miracle++ version 0.0.1 listening on port 5290
147 (7) Received signal 2 - shutting down.
149 Note that all commands except the PUSH are passed through this method before
150 they are executed and this includes those coming from the main function itself.
153 ACCESSING UNIT PROPERTIES
155 A unit consists of two objects - a playlist and a consumer. Your custom
156 server can access these by obtaining the Properties object associated to a unit
157 via the 'unit' method.
159 As a simple example we can replace our execute method above with the following:
161 Response *execute( char *command )
163 if ( !strcmp( command, "debug" ) )
166 while( unit( i ) != NULL )
167 unit( i ++ )->debug( );
168 return new Response( 200, "Diagnostics output" );
170 return Miracle::execute( command );
173 When this runs and you send a 'debug' command via DVCP, the server will output
174 some information on stderr, like:
176 (5) Starting server on 5290.
177 (5) Server version 0.0.1 listening on port 5290
178 (5) Connection established with localhost (7)
179 Object: [ ref=3, unit=0, generation=0, constructor=sdl, id=sdl, arg=(nil),
180 consumer=0x80716a0, playlist=0x807f8a8, root=/, notifier=0x8087c28 ]
181 (6) localhost "debug" 100
183 You can extract the objects using:
185 Playlist playlist( ( mlt_playlist )( unit( i )->get_data( "playlist" ) ) );
186 Consumer consumer( ( mlt_consumer )( unit( i )->get_data( "consumer" ) ) );
188 and use the standard MLT++ wrapping methods to interact with them or you can
189 bypass these and using the C API directly.
191 Obviously, this opens a lot of possibilities for the types of editing operations
192 than can be carried out over the DVCP protocol - for example, you can attach filters
193 apply mixes/transitions between neighbouring cuts or carry out specific operations
199 The example above doesn't do anything particularly useful - in order to extend
200 things in more interesting ways, we should be able to carry information back to
201 the client. In the code above, we introduced the Response object to carry an
202 error code and a description - it can also be used to carry arbitrary large
205 Response *execute( char *command )
207 Response *response = NULL;
208 if ( !strcmp( command, "debug" ) )
210 response = new Response( 200, "Diagnostics output" );
211 for( int i = 0; unit( i ) != NULL; i ++ )
213 Properties *properties = unit( i );
215 output << string( "Unit " ) << i << endl;
216 for ( int j = 0; j < properties->count( ); j ++ )
217 output << properties->get_name( j ) << " = " << properties->get( j ) << endl;
218 response->write( output.str( ).c_str( ) );
221 return response == NULL ? Miracle::execute( command ) : response;
224 Now when you connect to the server via a telnet session, you can access the
225 'debug' command as follows:
227 $ telnet localhost 5290
229 Connected to localhost (127.0.0.1).
230 Escape character is '^]'.
241 Note that the '200' return code specified is automatically promoted to a 201
242 because of the multiple lines.
244 Alternatively, you can invoke response->write as many times as you like - each
245 string submitted is simply appended to the object in a similar way to writing
246 to a file or socket. Note that the client doesn't receive anything until the
247 response is returned from this method (ie: there's currently no support to
248 stream results back to the client).
253 This feature allows you to pass MLT XML documents seamlessly from one
254 process to another and even to different computers on your network.
256 A server process would be running as follows:
258 #include <mlt++/Miracle>
263 Miracle miracle( "miracle", 5250 );
265 miracle.execute( "uadd sdl" );
266 miracle.execute( "play u0" );
267 miracle.wait_for_shutdown( );
271 Typically, when you have an MLT object such as a producer or a playlist,
272 you can send a XML representation of this to a running server with:
274 Consumer mvsp( "mvsp", "localhost:5250" );
275 mvsp.connect( producer );
278 The effect of the push will be to append the producer on to the first
281 HANDLING PUSHED DOCUMENTS
283 The custom class receives PUSH'd westley either via the received or push
286 The default handling is to simply append a pushed document on to the end of
289 You can test this in the server defined above from the command line, for
292 $ inigo noise: -consumer valerie:localhost:5290
294 By default, the 'push' method is used - this means that the xml document
295 received is automatically deserialised by the server itself and then offered
296 to the push method for handling - an example of this would be:
298 Response *push( char *command, Service *service )
300 Playlist playlist( ( mlt_playlist )( unit( 0 )->get_data( "playlist" ) ) );
301 Producer producer( *service );
302 if ( producer.is_valid( ) && playlist.is_valid( ) )
306 playlist.append( producer );
308 return new Response( 200, "OK" );
310 return new Response( 400, "Invalid" );
313 With this method, each service pushed into the server will automatically
314 replace whatever is currently playing.
316 Note that the 'received' method is not invoked by default - if you wish to
317 receive the XML document and carry out any additional processing prior to
318 processing, you should set the 'push-parser-off' property on the server to 1.
319 This can be done by placing the following line in your classes constructor:
321 set( "push-parser-off", 1 );
323 When this property is set, the received method is used instead of the push -
324 in this scenario, your implementation is responsible for all handling
327 To simulate this, you can try the following method:
329 Response *received( char *command, char *document )
332 Producer producer( "westley-xml", document );
333 return push( command, &producer );
336 When you push your videos in to the server via the inigo command above (or
337 from other tools, such as those in the shotcut suite), you will see the xml
338 in the servers stderr output. If you need to carry out some operations on the
339 xml document (such as replacing low quality videos used in the editing process
340 with their original) the received mechanism is the one that you would want to
346 What you do with the received MLT Service is largely up to you. As shown above,
347 you have flexibility in how the item is scheduled and you can carry out
348 manipulations on either the xml document and/or the deserialised producer.
350 Typically, shotcut and inigo produce 'tractor' objects - these can be easily
351 manipulated in the push method - for example, to remove a track from the
352 output, we could do something like:
354 Response *push( char *command, Service *service )
356 Playlist playlist( ( mlt_playlist )( unit( 0 )->get_data( "playlist" ) ) );
357 Tractor *tractor( *service );
358 if ( tractor.is_valid( ) && playlist.is_valid( ) )
360 // Remove track 2 (NB: tracks are indexed from 0 like everything else)
361 Producer *producer = tractor.track( 2 );
362 Playlist track( producer );
364 // If we have a valid track then hide video and audio
365 // This is a bit pattern - 1 is video, 2 is audio
366 if ( track.is_valid( ) )
367 track.set( "hide", 3 );
369 // You need to delete the reference to the playlist producer here
375 playlist.append( producer );
377 return new Response( 200, "OK" );
379 return new Response( 400, "Invalid" );
385 The MLT framework generates events which your custom server can use to do
386 various runtime manipulations. For the purpose of this document, I'll focus
387 on 'consumer-frame-render' - this event is fired immediately before a frame
390 See example in test/server.cpp
395 In some cases, it is desirable to fully disable the entire DVCP command set
396 and handle the PUSH in an application specific way (for example, the shotcut
397 applications all do this). The simplest way of doing this is to generate a
398 response that signifies the rejection of the command. In this example, the
399 'shutdown' command is also handled:
401 Response *execute( char *command )
403 if ( !strcmp( command, "shutdown" ) )
405 return new Response( 400, "Invalid Command" );
408 If you use this method in the code above, your server does nothing - no units
409 are defined, so even a PUSH will be rejected.