MLT++ library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

382 lines
11 KiB

  1. Server Customisation
  2. Copyright (C) 2005 Ushodaya Enterprises Limited
  3. Authors: Charles Yates <charles.yates@pandora.be>
  4. Last Revision: 2005-03-16
  5. INTRODUCTION
  6. This document describes how miracle can be customised. The emphasis is on
  7. showing simple examples of various aspects of the servers capabilities
  8. rather than on focussing on the MLT++ API.
  9. THE BASIC CUSTOM SERVER
  10. The most basic custom server exposes the entire DVCP protocol and is roughly
  11. equivalent to the miracle server iteself, but in this case, it lacks the
  12. initialisation from /etc/miracle.conf and the port is hardcoded to 5290:
  13. #include <iostream.h>
  14. using namespace std;
  15. #include <MltMiracle.h>
  16. using namespace Mlt;
  17. int main( int argc, char **argv )
  18. {
  19. Miracle server( "miracle++", 5290 );
  20. if ( server.start( ) )
  21. {
  22. server.execute( "uadd sdl" );
  23. server.execute( "play u0" );
  24. server.wait_for_shutdown( );
  25. }
  26. else
  27. {
  28. cerr << "Failed to start server" << endl;
  29. }
  30. return 0;
  31. }
  32. Note that after the server is started, this example submits the hard coded
  33. commands specified - further units and property settings can of course be
  34. specified via the DVCP protocol.
  35. To specify initial DVCP commands from /etc/miracle.conf, it is sufficient to
  36. specify an additional argument in the server constructor.
  37. The wait_for_shutdown call is not required if the server is integrated in
  38. a user interface application.
  39. CUSTOMISATION
  40. This document focusses on the following areas of customisation:
  41. * the Miracle server class
  42. * extending the command set
  43. * accessing the units
  44. * the Response object
  45. * handling pushed westley documents
  46. * accessiving events
  47. THE MIRACLE SERVER CLASS
  48. The full public interface of the server is as follows:
  49. class Miracle : public Properties
  50. {
  51. public:
  52. Miracle( char *name, int port = 5290, char *config = NULL );
  53. virtual ~Miracle( );
  54. mlt_properties get_properties( );
  55. bool start( );
  56. bool is_running( );
  57. virtual Response *execute( char *command );
  58. virtual Response *received( char *command, char *doc );
  59. virtual Response *push( char *command, Service *service );
  60. void wait_for_shutdown( );
  61. static void log_level( int );
  62. Properties *unit( int );
  63. };
  64. The focus of this document is on the 3 virtual methods (execute, received and
  65. push). Some further information is provided about the unit properties method
  66. and the types of functionality that it provides.
  67. EXTENDING THE COMMAND SET
  68. The simplest customisation is carried out by overriding the the 'execute'
  69. method - the following shows a simple example:
  70. #include <iostream.h>
  71. #include <string>
  72. #include <sstring>
  73. using namespace std;
  74. #include <MltMiracle.h>
  75. #include <MltResponse.h>
  76. using namespace Mlt;
  77. class Custom :
  78. public Miracle
  79. {
  80. public:
  81. Custom( char *name = "Custom", int port = 5290, char *config = NULL ) :
  82. Miracle( name, port, config )
  83. {
  84. }
  85. Response *execute( char *command )
  86. {
  87. cerr << "command = " << command << endl;
  88. return Miracle::execute( command );
  89. }
  90. };
  91. int main( int argc, char **argv )
  92. {
  93. Custom server( "miracle++", 5290 );
  94. if ( server.start( ) )
  95. {
  96. server.execute( "uadd sdl" );
  97. server.execute( "play u0" );
  98. server.wait_for_shutdown( );
  99. }
  100. else
  101. {
  102. cerr << "Failed to start server" << endl;
  103. }
  104. return 0;
  105. }
  106. All this does is output each command and pass control over to the original
  107. implementation.
  108. When you execute this, you will see the following output:
  109. (5) Starting server on 5290.
  110. command = uadd sdl
  111. (5) miracle++ version 0.0.1 listening on port 5290
  112. command = play u0
  113. (7) Received signal 2 - shutting down.
  114. Note that all commands except the PUSH are passed through this method before
  115. they are executed and this includes those coming from the main function itself.
  116. ACCESSING UNIT PROPERTIES
  117. A unit consists of two objects - a playlist and a consumer. Your custom
  118. server can access these by obtaining the Properties object associated to a unit
  119. via the 'unit' method.
  120. As a simple example we can replace our execute method above with the following:
  121. Response *execute( char *command )
  122. {
  123. if ( !strcmp( command, "debug" ) )
  124. {
  125. int i = 0;
  126. while( unit( i ) != NULL )
  127. unit( i ++ )->debug( );
  128. return new Response( 200, "Diagnostics output" );
  129. }
  130. return Miracle::execute( command );
  131. }
  132. When this runs and you send a 'debug' command via DVCP, the server will output
  133. some information on stderr, like:
  134. (5) Starting server on 5290.
  135. (5) Server version 0.0.1 listening on port 5290
  136. (5) Connection established with localhost (7)
  137. Object: [ ref=3, unit=0, generation=0, constructor=sdl, id=sdl, arg=(nil),
  138. consumer=0x80716a0, playlist=0x807f8a8, root=/, notifier=0x8087c28 ]
  139. (6) localhost "debug" 100
  140. You can extract the objects using:
  141. Playlist playlist( ( mlt_playlist )( unit( i )->get_data( "playlist" ) ) );
  142. Consumer consumer( ( mlt_consumer )( unit( i )->get_data( "consumer" ) ) );
  143. and use the standard MLT++ wrapping methods to interact with them or you can
  144. bypass these and using the C API directly.
  145. Obviously, this opens a lot of possibilities for the types of editing operations
  146. than can be carried out over the DVCP protocol - for example, you can attach filters
  147. apply mixes/transitions between neighbouring cuts or carry out specific operations
  148. on cuts.
  149. THE RESPONSE OBJECT
  150. The example above doesn't do anything particularly useful - in order to extend
  151. things in more interesting ways, we should be able to carry information back to
  152. the client. In the code above, we introduced the Response object to carry an
  153. error code and a description - it can also be used to carry arbitrary large
  154. blocks of data.
  155. Response *execute( char *command )
  156. {
  157. Response *response = NULL;
  158. if ( !strcmp( command, "debug" ) )
  159. {
  160. response = new Response( 200, "Diagnostics output" );
  161. for( int i = 0; unit( i ) != NULL; i ++ )
  162. {
  163. Properties *properties = unit( i );
  164. stringstream output;
  165. output << string( "Unit " ) << i << endl;
  166. for ( int j = 0; j < properties->count( ); j ++ )
  167. output << properties->get_name( j ) << " = " << properties->get( j ) << endl;
  168. response->write( output.str( ).c_str( ) );
  169. }
  170. }
  171. return response == NULL ? Miracle::execute( command ) : response;
  172. }
  173. Now when you connect to the server via a telnet session, you can access the
  174. 'debug' command as follows:
  175. $ telnet localhost 5290
  176. Trying 127.0.0.1...
  177. Connected to localhost (127.0.0.1).
  178. Escape character is '^]'.
  179. 100 VTR Ready
  180. debug
  181. 201 OK
  182. Unit 0
  183. unit = 0
  184. generation = 0
  185. constructor = sdl
  186. id = sdl
  187. arg =
  188. Note that the '200' return code specified is automatically promoted to a 201
  189. because of the multiple lines.
  190. Alternatively, you can invoke response->write as many times as you like - each
  191. string submitted is simply appended to the object in a similar way to writing
  192. to a file or socket. Note that the client doesn't receive anything until the
  193. response is returned from this method (ie: there's currently no support to
  194. stream results back to the client).
  195. HANDLING PUSHED DOCUMENTS
  196. The custom class receives PUSH'd westley either via the received or push
  197. method.
  198. The default handling is to simply append a pushed document on to the end of
  199. first unit 0.
  200. You can test this in the server defined above from the command line, for
  201. example:
  202. $ inigo noise: -consumer valerie:localhost:5290
  203. By default, the 'push' method is used - this means that the xml document
  204. received is automatically deserialised by the server itself and then offered
  205. to the push method for handling - an example of this would be:
  206. Response *push( char *command, Service *service )
  207. {
  208. Playlist playlist( ( mlt_playlist )( unit( 0 )->get_data( "playlist" ) ) );
  209. Producer producer( *service );
  210. if ( producer.is_valid( ) && playlist.is_valid( ) )
  211. {
  212. playlist.lock( );
  213. playlist.clear( );
  214. playlist.append( producer );
  215. playlist.unlock( );
  216. return new Response( 200, "OK" );
  217. }
  218. return new Response( 400, "Invalid" );
  219. }
  220. With this method, each service pushed into the server will automatically
  221. replace whatever is currently playing.
  222. Note that the 'received' method is not invoked by default - if you wish to
  223. receive the XML document and carry out any additional processing prior to
  224. processing, you should set the 'push-parser-off' property on the server to 1.
  225. This can be done by placing the following line in your classes constructor:
  226. set( "push-parser-off", 1 );
  227. When this property is set, the received method is used instead of the push -
  228. in this scenario, your implementation is responsible for all handling
  229. of the document.
  230. To simulate this, you can try the following method:
  231. Response *received( char *command, char *document )
  232. {
  233. cerr << document;
  234. Producer producer( "westley-xml", document );
  235. return push( command, &producer );
  236. }
  237. When you push your videos in to the server via the inigo command above (or
  238. from other tools, such as those in the shotcut suite), you will see the xml
  239. in the servers stderr output. If you need to carry out some operations on the
  240. xml document (such as replacing low quality videos used in the editing process
  241. with their original) the received mechanism is the one that you would want to
  242. use.
  243. OTHER MANIPULATIONS
  244. What you do with the received MLT Service is largely up to you. As shown above,
  245. you have flexibility in how the item is scheduled and you can carry out
  246. manipulations on either the xml document and/or the deserialised producer.
  247. Typically, shotcut and inigo produce 'tractor' objects - these can be easily
  248. manipulated in the push method - for example, to remove a track from the
  249. output, we could do something like:
  250. Response *push( char *command, Service *service )
  251. {
  252. Playlist playlist( ( mlt_playlist )( unit( 0 )->get_data( "playlist" ) ) );
  253. Tractor *tractor( *service );
  254. if ( tractor.is_valid( ) && playlist.is_valid( ) )
  255. {
  256. // Remove track 2 (NB: tracks are indexed from 0 like everything else)
  257. Producer *producer = tractor.track( 2 );
  258. Playlist track( producer );
  259. // If we have a valid track then hide video and audio
  260. // This is a bit pattern - 1 is video, 2 is audio
  261. if ( track.is_valid( ) )
  262. track.set( "hide", 3 );
  263. // You need to delete the reference to the playlist producer here
  264. delete producer;
  265. // Play it
  266. playlist.lock( );
  267. playlist.clear( );
  268. playlist.append( producer );
  269. playlist.unlock( );
  270. return new Response( 200, "OK" );
  271. }
  272. return new Response( 400, "Invalid" );
  273. }
  274. EVENT HANDLING
  275. The MLT framework generates events which your custom server can use to do
  276. various runtime manipulations. For the purpose of this document, I'll focus
  277. on 'consumer-frame-render' - this event is fired immediately before a frame
  278. is rendered.
  279. See example in test/server.cpp
  280. DISABLING DVCP
  281. In some cases, it is desirable to fully disable the entire DVCP command set
  282. and handle the PUSH in an application specific way (for example, the shotcut
  283. applications all do this). The simplest way of doing this is to generate a
  284. response that signifies the rejection of the command. In this example, the
  285. 'shutdown' command is also handled:
  286. Response *execute( char *command )
  287. {
  288. if ( !strcmp( command, "shutdown" ) )
  289. exit( 0 );
  290. return new Response( 400, "Invalid Command" );
  291. }
  292. If you use this method in the code above, your server does nothing - no units
  293. are defined, so even a PUSH will be rejected.