From f5a43889f0ea29962f2d32b5dab0fcd25bebb945 Mon Sep 17 00:00:00 2001 From: Dan Dennedy Date: Thu, 14 May 2009 22:05:10 -0700 Subject: [PATCH] Merge mlt++/CUSTOMISING into docs/melted++. Signed-off-by: Dan Dennedy --- docs/melted++.txt | 399 +++++++++++++++++++++++++++++++++++++++++++++++++++-- mlt++/CUSTOMISING | 381 -------------------------------------------------- 2 files changed, 385 insertions(+), 395 deletions(-) delete mode 100644 mlt++/CUSTOMISING diff --git a/docs/melted++.txt b/docs/melted++.txt index 843dea4..711d901 100644 --- a/docs/melted++.txt +++ b/docs/melted++.txt @@ -1,13 +1,257 @@ -Servers and Westley Docs ------------------------- +Server Customisation - One of the key features of MLT is its server capabilities. This feature - allows you to pass westley documents seamlessly from one process to - another and even to different computers on your network. +Copyright (C) 2005-2009 Ushodaya Enterprises Limited +Authors: Charles Yates +Last Revision: 2009-05-14 - The miracle playout server is one such example of an application which - uses this functionality - you can build your own servers into your own - processes with ease. + +INTRODUCTION + + This document describes how miracle can be customised. The emphasis is on + showing simple examples of various aspects of the servers capabilities + rather than on focussing on the MLT++ API. + + +THE BASIC CUSTOM SERVER + + The most basic custom server exposes the entire DVCP protocol and is roughly + equivalent to the miracle server iteself, but in this case, it lacks the + initialisation from /etc/miracle.conf and the port is hardcoded to 5290: + + #include + using namespace std; + + #include + using namespace Mlt; + + int main( int argc, char **argv ) + { + Miracle server( "miracle++", 5290 ); + if ( server.start( ) ) + { + server.execute( "uadd sdl" ); + server.execute( "play u0" ); + server.wait_for_shutdown( ); + } + else + { + cerr << "Failed to start server" << endl; + } + return 0; + } + + Note that after the server is started, this example submits the hard coded + commands specified - further units and property settings can of course be + specified via the DVCP protocol. + + To specify initial DVCP commands from /etc/miracle.conf, it is sufficient to + specify an additional argument in the server constructor. + + The wait_for_shutdown call is not required if the server is integrated in + a user interface application. + + +CUSTOMISATION + + This document focusses on the following areas of customisation: + + * the Miracle server class + * extending the command set + * accessing the units + * the Response object + * pushing documents + * handling pushed documents + * accessiving events + + +THE MIRACLE SERVER CLASS + + The full public interface of the server is as follows: + + class Miracle : public Properties + { + public: + Miracle( char *name, int port = 5290, char *config = NULL ); + virtual ~Miracle( ); + mlt_properties get_properties( ); + bool start( ); + bool is_running( ); + virtual Response *execute( char *command ); + virtual Response *received( char *command, char *doc ); + virtual Response *push( char *command, Service *service ); + void wait_for_shutdown( ); + static void log_level( int ); + Properties *unit( int ); + }; + + The focus of this document is on the 3 virtual methods (execute, received and + push). Some further information is provided about the unit properties method + and the types of functionality that it provides. + + +EXTENDING THE COMMAND SET + + The simplest customisation is carried out by overriding the the 'execute' + method - the following shows a simple example: + + #include + #include + #include + using namespace std; + + #include + #include + using namespace Mlt; + + class Custom : + public Miracle + { + public: + Custom( char *name = "Custom", int port = 5290, char *config = NULL ) : + Miracle( name, port, config ) + { + } + + Response *execute( char *command ) + { + cerr << "command = " << command << endl; + return Miracle::execute( command ); + } + }; + + int main( int argc, char **argv ) + { + Custom server( "miracle++", 5290 ); + if ( server.start( ) ) + { + server.execute( "uadd sdl" ); + server.execute( "play u0" ); + server.wait_for_shutdown( ); + } + else + { + cerr << "Failed to start server" << endl; + } + return 0; + } + + All this does is output each command and pass control over to the original + implementation. + + When you execute this, you will see the following output: + + (5) Starting server on 5290. + command = uadd sdl + (5) miracle++ version 0.0.1 listening on port 5290 + command = play u0 + (7) Received signal 2 - shutting down. + + Note that all commands except the PUSH are passed through this method before + they are executed and this includes those coming from the main function itself. + + +ACCESSING UNIT PROPERTIES + + A unit consists of two objects - a playlist and a consumer. Your custom + server can access these by obtaining the Properties object associated to a unit + via the 'unit' method. + + As a simple example we can replace our execute method above with the following: + + Response *execute( char *command ) + { + if ( !strcmp( command, "debug" ) ) + { + int i = 0; + while( unit( i ) != NULL ) + unit( i ++ )->debug( ); + return new Response( 200, "Diagnostics output" ); + } + return Miracle::execute( command ); + } + + When this runs and you send a 'debug' command via DVCP, the server will output + some information on stderr, like: + + (5) Starting server on 5290. + (5) Server version 0.0.1 listening on port 5290 + (5) Connection established with localhost (7) + Object: [ ref=3, unit=0, generation=0, constructor=sdl, id=sdl, arg=(nil), + consumer=0x80716a0, playlist=0x807f8a8, root=/, notifier=0x8087c28 ] + (6) localhost "debug" 100 + + You can extract the objects using: + + Playlist playlist( ( mlt_playlist )( unit( i )->get_data( "playlist" ) ) ); + Consumer consumer( ( mlt_consumer )( unit( i )->get_data( "consumer" ) ) ); + + and use the standard MLT++ wrapping methods to interact with them or you can + bypass these and using the C API directly. + + Obviously, this opens a lot of possibilities for the types of editing operations + than can be carried out over the DVCP protocol - for example, you can attach filters + apply mixes/transitions between neighbouring cuts or carry out specific operations + on cuts. + + +THE RESPONSE OBJECT + + The example above doesn't do anything particularly useful - in order to extend + things in more interesting ways, we should be able to carry information back to + the client. In the code above, we introduced the Response object to carry an + error code and a description - it can also be used to carry arbitrary large + blocks of data. + + Response *execute( char *command ) + { + Response *response = NULL; + if ( !strcmp( command, "debug" ) ) + { + response = new Response( 200, "Diagnostics output" ); + for( int i = 0; unit( i ) != NULL; i ++ ) + { + Properties *properties = unit( i ); + stringstream output; + output << string( "Unit " ) << i << endl; + for ( int j = 0; j < properties->count( ); j ++ ) + output << properties->get_name( j ) << " = " << properties->get( j ) << endl; + response->write( output.str( ).c_str( ) ); + } + } + return response == NULL ? Miracle::execute( command ) : response; + } + + Now when you connect to the server via a telnet session, you can access the + 'debug' command as follows: + + $ telnet localhost 5290 + Trying 127.0.0.1... + Connected to localhost (127.0.0.1). + Escape character is '^]'. + 100 VTR Ready + debug + 201 OK + Unit 0 + unit = 0 + generation = 0 + constructor = sdl + id = sdl + arg = + + Note that the '200' return code specified is automatically promoted to a 201 + because of the multiple lines. + + Alternatively, you can invoke response->write as many times as you like - each + string submitted is simply appended to the object in a similar way to writing + to a file or socket. Note that the client doesn't receive anything until the + response is returned from this method (ie: there's currently no support to + stream results back to the client). + + +PUSHING DOCUMENTS + + This feature allows you to pass MLT XML documents seamlessly from one + process to another and even to different computers on your network. A server process would be running as follows: @@ -25,14 +269,141 @@ Servers and Westley Docs } Typically, when you have an MLT object such as a producer or a playlist, - you can send a westley representation of this to a running server with: + you can send a XML representation of this to a running server with: - Conumser valerie( "valerie", "localhost:5250" ); - valerie.connect( producer ); - valerie.start( ); + Consumer mvsp( "mvsp", "localhost:5250" ); + mvsp.connect( producer ); + mvsp.start( ); The effect of the push will be to append the producer on to the first unit (u0). - You can completely customise the miracle server - an example of this - is shown below. +HANDLING PUSHED DOCUMENTS + + The custom class receives PUSH'd westley either via the received or push + method. + + The default handling is to simply append a pushed document on to the end of + first unit 0. + + You can test this in the server defined above from the command line, for + example: + + $ inigo noise: -consumer valerie:localhost:5290 + + By default, the 'push' method is used - this means that the xml document + received is automatically deserialised by the server itself and then offered + to the push method for handling - an example of this would be: + + Response *push( char *command, Service *service ) + { + Playlist playlist( ( mlt_playlist )( unit( 0 )->get_data( "playlist" ) ) ); + Producer producer( *service ); + if ( producer.is_valid( ) && playlist.is_valid( ) ) + { + playlist.lock( ); + playlist.clear( ); + playlist.append( producer ); + playlist.unlock( ); + return new Response( 200, "OK" ); + } + return new Response( 400, "Invalid" ); + } + + With this method, each service pushed into the server will automatically + replace whatever is currently playing. + + Note that the 'received' method is not invoked by default - if you wish to + receive the XML document and carry out any additional processing prior to + processing, you should set the 'push-parser-off' property on the server to 1. + This can be done by placing the following line in your classes constructor: + + set( "push-parser-off", 1 ); + + When this property is set, the received method is used instead of the push - + in this scenario, your implementation is responsible for all handling + of the document. + + To simulate this, you can try the following method: + + Response *received( char *command, char *document ) + { + cerr << document; + Producer producer( "westley-xml", document ); + return push( command, &producer ); + } + + When you push your videos in to the server via the inigo command above (or + from other tools, such as those in the shotcut suite), you will see the xml + in the servers stderr output. If you need to carry out some operations on the + xml document (such as replacing low quality videos used in the editing process + with their original) the received mechanism is the one that you would want to + use. + + +OTHER MANIPULATIONS + + What you do with the received MLT Service is largely up to you. As shown above, + you have flexibility in how the item is scheduled and you can carry out + manipulations on either the xml document and/or the deserialised producer. + + Typically, shotcut and inigo produce 'tractor' objects - these can be easily + manipulated in the push method - for example, to remove a track from the + output, we could do something like: + + Response *push( char *command, Service *service ) + { + Playlist playlist( ( mlt_playlist )( unit( 0 )->get_data( "playlist" ) ) ); + Tractor *tractor( *service ); + if ( tractor.is_valid( ) && playlist.is_valid( ) ) + { + // Remove track 2 (NB: tracks are indexed from 0 like everything else) + Producer *producer = tractor.track( 2 ); + Playlist track( producer ); + + // If we have a valid track then hide video and audio + // This is a bit pattern - 1 is video, 2 is audio + if ( track.is_valid( ) ) + track.set( "hide", 3 ); + + // You need to delete the reference to the playlist producer here + delete producer; + + // Play it + playlist.lock( ); + playlist.clear( ); + playlist.append( producer ); + playlist.unlock( ); + return new Response( 200, "OK" ); + } + return new Response( 400, "Invalid" ); + } + + +EVENT HANDLING + + The MLT framework generates events which your custom server can use to do + various runtime manipulations. For the purpose of this document, I'll focus + on 'consumer-frame-render' - this event is fired immediately before a frame + is rendered. + + See example in test/server.cpp + + +DISABLING DVCP + + In some cases, it is desirable to fully disable the entire DVCP command set + and handle the PUSH in an application specific way (for example, the shotcut + applications all do this). The simplest way of doing this is to generate a + response that signifies the rejection of the command. In this example, the + 'shutdown' command is also handled: + + Response *execute( char *command ) + { + if ( !strcmp( command, "shutdown" ) ) + exit( 0 ); + return new Response( 400, "Invalid Command" ); + } + + If you use this method in the code above, your server does nothing - no units + are defined, so even a PUSH will be rejected. diff --git a/mlt++/CUSTOMISING b/mlt++/CUSTOMISING deleted file mode 100644 index 7fdce7f..0000000 --- a/mlt++/CUSTOMISING +++ /dev/null @@ -1,381 +0,0 @@ -Server Customisation - -Copyright (C) 2005 Ushodaya Enterprises Limited -Authors: Charles Yates -Last Revision: 2005-03-16 - - -INTRODUCTION - - This document describes how miracle can be customised. The emphasis is on - showing simple examples of various aspects of the servers capabilities - rather than on focussing on the MLT++ API. - - -THE BASIC CUSTOM SERVER - - The most basic custom server exposes the entire DVCP protocol and is roughly - equivalent to the miracle server iteself, but in this case, it lacks the - initialisation from /etc/miracle.conf and the port is hardcoded to 5290: - - #include - using namespace std; - - #include - using namespace Mlt; - - int main( int argc, char **argv ) - { - Miracle server( "miracle++", 5290 ); - if ( server.start( ) ) - { - server.execute( "uadd sdl" ); - server.execute( "play u0" ); - server.wait_for_shutdown( ); - } - else - { - cerr << "Failed to start server" << endl; - } - return 0; - } - - Note that after the server is started, this example submits the hard coded - commands specified - further units and property settings can of course be - specified via the DVCP protocol. - - To specify initial DVCP commands from /etc/miracle.conf, it is sufficient to - specify an additional argument in the server constructor. - - The wait_for_shutdown call is not required if the server is integrated in - a user interface application. - - -CUSTOMISATION - - This document focusses on the following areas of customisation: - - * the Miracle server class - * extending the command set - * accessing the units - * the Response object - * handling pushed westley documents - * accessiving events - - -THE MIRACLE SERVER CLASS - - The full public interface of the server is as follows: - - class Miracle : public Properties - { - public: - Miracle( char *name, int port = 5290, char *config = NULL ); - virtual ~Miracle( ); - mlt_properties get_properties( ); - bool start( ); - bool is_running( ); - virtual Response *execute( char *command ); - virtual Response *received( char *command, char *doc ); - virtual Response *push( char *command, Service *service ); - void wait_for_shutdown( ); - static void log_level( int ); - Properties *unit( int ); - }; - - The focus of this document is on the 3 virtual methods (execute, received and - push). Some further information is provided about the unit properties method - and the types of functionality that it provides. - - -EXTENDING THE COMMAND SET - - The simplest customisation is carried out by overriding the the 'execute' - method - the following shows a simple example: - - #include - #include - #include - using namespace std; - - #include - #include - using namespace Mlt; - - class Custom : - public Miracle - { - public: - Custom( char *name = "Custom", int port = 5290, char *config = NULL ) : - Miracle( name, port, config ) - { - } - - Response *execute( char *command ) - { - cerr << "command = " << command << endl; - return Miracle::execute( command ); - } - }; - - int main( int argc, char **argv ) - { - Custom server( "miracle++", 5290 ); - if ( server.start( ) ) - { - server.execute( "uadd sdl" ); - server.execute( "play u0" ); - server.wait_for_shutdown( ); - } - else - { - cerr << "Failed to start server" << endl; - } - return 0; - } - - All this does is output each command and pass control over to the original - implementation. - - When you execute this, you will see the following output: - - (5) Starting server on 5290. - command = uadd sdl - (5) miracle++ version 0.0.1 listening on port 5290 - command = play u0 - (7) Received signal 2 - shutting down. - - Note that all commands except the PUSH are passed through this method before - they are executed and this includes those coming from the main function itself. - - -ACCESSING UNIT PROPERTIES - - A unit consists of two objects - a playlist and a consumer. Your custom - server can access these by obtaining the Properties object associated to a unit - via the 'unit' method. - - As a simple example we can replace our execute method above with the following: - - Response *execute( char *command ) - { - if ( !strcmp( command, "debug" ) ) - { - int i = 0; - while( unit( i ) != NULL ) - unit( i ++ )->debug( ); - return new Response( 200, "Diagnostics output" ); - } - return Miracle::execute( command ); - } - - When this runs and you send a 'debug' command via DVCP, the server will output - some information on stderr, like: - - (5) Starting server on 5290. - (5) Server version 0.0.1 listening on port 5290 - (5) Connection established with localhost (7) - Object: [ ref=3, unit=0, generation=0, constructor=sdl, id=sdl, arg=(nil), - consumer=0x80716a0, playlist=0x807f8a8, root=/, notifier=0x8087c28 ] - (6) localhost "debug" 100 - - You can extract the objects using: - - Playlist playlist( ( mlt_playlist )( unit( i )->get_data( "playlist" ) ) ); - Consumer consumer( ( mlt_consumer )( unit( i )->get_data( "consumer" ) ) ); - - and use the standard MLT++ wrapping methods to interact with them or you can - bypass these and using the C API directly. - - Obviously, this opens a lot of possibilities for the types of editing operations - than can be carried out over the DVCP protocol - for example, you can attach filters - apply mixes/transitions between neighbouring cuts or carry out specific operations - on cuts. - - -THE RESPONSE OBJECT - - The example above doesn't do anything particularly useful - in order to extend - things in more interesting ways, we should be able to carry information back to - the client. In the code above, we introduced the Response object to carry an - error code and a description - it can also be used to carry arbitrary large - blocks of data. - - Response *execute( char *command ) - { - Response *response = NULL; - if ( !strcmp( command, "debug" ) ) - { - response = new Response( 200, "Diagnostics output" ); - for( int i = 0; unit( i ) != NULL; i ++ ) - { - Properties *properties = unit( i ); - stringstream output; - output << string( "Unit " ) << i << endl; - for ( int j = 0; j < properties->count( ); j ++ ) - output << properties->get_name( j ) << " = " << properties->get( j ) << endl; - response->write( output.str( ).c_str( ) ); - } - } - return response == NULL ? Miracle::execute( command ) : response; - } - - Now when you connect to the server via a telnet session, you can access the - 'debug' command as follows: - - $ telnet localhost 5290 - Trying 127.0.0.1... - Connected to localhost (127.0.0.1). - Escape character is '^]'. - 100 VTR Ready - debug - 201 OK - Unit 0 - unit = 0 - generation = 0 - constructor = sdl - id = sdl - arg = - - Note that the '200' return code specified is automatically promoted to a 201 - because of the multiple lines. - - Alternatively, you can invoke response->write as many times as you like - each - string submitted is simply appended to the object in a similar way to writing - to a file or socket. Note that the client doesn't receive anything until the - response is returned from this method (ie: there's currently no support to - stream results back to the client). - - -HANDLING PUSHED DOCUMENTS - - The custom class receives PUSH'd westley either via the received or push - method. - - The default handling is to simply append a pushed document on to the end of - first unit 0. - - You can test this in the server defined above from the command line, for - example: - - $ inigo noise: -consumer valerie:localhost:5290 - - By default, the 'push' method is used - this means that the xml document - received is automatically deserialised by the server itself and then offered - to the push method for handling - an example of this would be: - - Response *push( char *command, Service *service ) - { - Playlist playlist( ( mlt_playlist )( unit( 0 )->get_data( "playlist" ) ) ); - Producer producer( *service ); - if ( producer.is_valid( ) && playlist.is_valid( ) ) - { - playlist.lock( ); - playlist.clear( ); - playlist.append( producer ); - playlist.unlock( ); - return new Response( 200, "OK" ); - } - return new Response( 400, "Invalid" ); - } - - With this method, each service pushed into the server will automatically - replace whatever is currently playing. - - Note that the 'received' method is not invoked by default - if you wish to - receive the XML document and carry out any additional processing prior to - processing, you should set the 'push-parser-off' property on the server to 1. - This can be done by placing the following line in your classes constructor: - - set( "push-parser-off", 1 ); - - When this property is set, the received method is used instead of the push - - in this scenario, your implementation is responsible for all handling - of the document. - - To simulate this, you can try the following method: - - Response *received( char *command, char *document ) - { - cerr << document; - Producer producer( "westley-xml", document ); - return push( command, &producer ); - } - - When you push your videos in to the server via the inigo command above (or - from other tools, such as those in the shotcut suite), you will see the xml - in the servers stderr output. If you need to carry out some operations on the - xml document (such as replacing low quality videos used in the editing process - with their original) the received mechanism is the one that you would want to - use. - - -OTHER MANIPULATIONS - - What you do with the received MLT Service is largely up to you. As shown above, - you have flexibility in how the item is scheduled and you can carry out - manipulations on either the xml document and/or the deserialised producer. - - Typically, shotcut and inigo produce 'tractor' objects - these can be easily - manipulated in the push method - for example, to remove a track from the - output, we could do something like: - - Response *push( char *command, Service *service ) - { - Playlist playlist( ( mlt_playlist )( unit( 0 )->get_data( "playlist" ) ) ); - Tractor *tractor( *service ); - if ( tractor.is_valid( ) && playlist.is_valid( ) ) - { - // Remove track 2 (NB: tracks are indexed from 0 like everything else) - Producer *producer = tractor.track( 2 ); - Playlist track( producer ); - - // If we have a valid track then hide video and audio - // This is a bit pattern - 1 is video, 2 is audio - if ( track.is_valid( ) ) - track.set( "hide", 3 ); - - // You need to delete the reference to the playlist producer here - delete producer; - - // Play it - playlist.lock( ); - playlist.clear( ); - playlist.append( producer ); - playlist.unlock( ); - return new Response( 200, "OK" ); - } - return new Response( 400, "Invalid" ); - } - - -EVENT HANDLING - - The MLT framework generates events which your custom server can use to do - various runtime manipulations. For the purpose of this document, I'll focus - on 'consumer-frame-render' - this event is fired immediately before a frame - is rendered. - - See example in test/server.cpp - - -DISABLING DVCP - - In some cases, it is desirable to fully disable the entire DVCP command set - and handle the PUSH in an application specific way (for example, the shotcut - applications all do this). The simplest way of doing this is to generate a - response that signifies the rejection of the command. In this example, the - 'shutdown' command is also handled: - - Response *execute( char *command ) - { - if ( !strcmp( command, "shutdown" ) ) - exit( 0 ); - return new Response( 400, "Invalid Command" ); - } - - If you use this method in the code above, your server does nothing - no units - are defined, so even a PUSH will be rejected. - - - -- 1.7.4.4