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.
474 lines
15 KiB
474 lines
15 KiB
INTRODUCTION
|
|
------------
|
|
|
|
This document provides a brief tutorial on the use of the mlt++ wrapper
|
|
and bindings.
|
|
|
|
|
|
Hello World
|
|
-----------
|
|
|
|
The mlt++ wrapper is a c++ wrapper for the mlt C library. As such, it
|
|
provides clean C++ access to the underlying library.
|
|
|
|
An example of use is as follows:
|
|
|
|
#include <mlt++/Mlt.h>
|
|
using namespace Mlt;
|
|
|
|
int main( void )
|
|
{
|
|
Factory::init( );
|
|
Producer p( "pango:", "Hello World" );
|
|
Consumer c( "sdl" );
|
|
c.connect( p );
|
|
c.run( );
|
|
return 0;
|
|
}
|
|
|
|
This is a fairly typical example of mlt++ usage - create a 'producer' (an
|
|
object which produces 'frames'), create a 'consumer' (an object which consumes
|
|
frames), connect them together, start the consumer and wait until done (here
|
|
we just wait for the user to close the window).
|
|
|
|
In this case, we construct a window as a consumer using the 'sdl' consumer
|
|
(SDL is a standard portable library which provides platform independent
|
|
access to accelerated video display and audio) and use the 'pango'
|
|
producer to generate frames with the words 'Hello World' (pango is a
|
|
library from the gtk toolkit).
|
|
|
|
The main point of this example is to show that mlt uses existing libraries
|
|
to provide its functionality - this keeps the framework itself very small.
|
|
|
|
Note that mlt is designed to be housed in GUI or server type applications -
|
|
typically, applications don't wait around for the consumer to be stopped in
|
|
the manner shown.
|
|
|
|
So far, we've introduced the Producer and Consumer mlt classes. We'll cover
|
|
each of these in more detail later in the tutorial, but for now, we'll
|
|
briefly cover the remaining classes.
|
|
|
|
|
|
Playlists
|
|
---------
|
|
|
|
Another simple class is the Playlist - this is direct extension of Producer
|
|
and it allows you to maintain a list of producer objects.
|
|
|
|
As a simple example of the Playlist in action, we'll convert the example
|
|
above into an application which plays multiple video or audio files.
|
|
|
|
#include <mlt++/Mlt.h>
|
|
using namespace Mlt;
|
|
|
|
int main( int argc, char **argv )
|
|
{
|
|
Factory::init( );
|
|
Playlist list;
|
|
for ( int i = 1; i < argc; i ++ )
|
|
{
|
|
Producer p( argv[i] );
|
|
if ( p.is_valid( ) )
|
|
list.append( p );
|
|
}
|
|
Consumer c( "sdl" );
|
|
c.connect( list );
|
|
c.run( );
|
|
return 0;
|
|
}
|
|
|
|
Now you can run the program as:
|
|
|
|
./player *.avi *.mp3 *.jpg etc
|
|
|
|
In this case, we construct a playlist by simply appending producers to it.
|
|
Notice that although the scope of the Producer is limited to the inner
|
|
for loop, we can safely add it to the playlist - this is due to the fact
|
|
that all mlt objects maintain reference counts and no object is really
|
|
destroyed until all the references are gone. In this case, when the list
|
|
object goes out of scope, all the producers we created will automatically
|
|
be destroyed.
|
|
|
|
|
|
Filters
|
|
-------
|
|
|
|
So far, we've shown how you can load and play media. We've given a brief
|
|
intro to the Playlist container, now it's time to start manipulating
|
|
things...
|
|
|
|
For the next example, I'll add a 'watermark' to the video - a watermark
|
|
is used by broadcasters to brand the channel and normally consists of a
|
|
logo of some sort. We'll just use some black text on a partially
|
|
transparent red background.
|
|
|
|
#include <mlt++/Mlt.h>
|
|
using namespace Mlt;
|
|
|
|
int main( int argc, char **argv )
|
|
{
|
|
Factory::init( );
|
|
Playlist list;
|
|
for ( int i = 1; i < argc; i ++ )
|
|
{
|
|
Producer p( argv[i] );
|
|
if ( p.is_valid( ) )
|
|
list.append( p );
|
|
}
|
|
Filter f( "watermark", "pango:" );
|
|
f.set( "producer.text", "MLT++" );
|
|
f.set( "producer.fgcolour", "0x000000ff" );
|
|
f.set( "producer.bgcolour", "0xff000080" );
|
|
list.attach( f );
|
|
Consumer c( "sdl" );
|
|
c.connect( list );
|
|
c.run( );
|
|
return 0;
|
|
}
|
|
|
|
Notice that the watermark filter reuses the 'pango' producer we showed in the
|
|
first example. In fact, you could use any producer here - if you wanted to
|
|
use a graphic or a video, you would just construct the filter with a full path
|
|
to that as the second argument.
|
|
|
|
We manipulate the filter using the set method - this method was also shown
|
|
in the first example.
|
|
|
|
Finally, we attach the filter to the playlist. This ensure that all frames
|
|
that are obtained from the playlist are watermarked.
|
|
|
|
|
|
Cuts
|
|
----
|
|
|
|
When you add a clip to a playlist, the a cut object is created - this is merely a
|
|
wrapper for the producer, spanning the specified in and out points.
|
|
|
|
Whenever you retrieve a clip from a playlist, you will always get a cut object.
|
|
This allows you to attach filters to a specific part of a producer and should
|
|
the position of the cut in the playlist change, then the filter will remain
|
|
correctly associated to it.
|
|
|
|
A producer and a cut are generally identical in behaviour, but should you need to
|
|
distinguish between them, you can use:
|
|
|
|
if ( producer.is_cut( ) )
|
|
|
|
and to retrieve the parent of a cut, you can use:
|
|
|
|
Producer parent = producer.parent_cut( );
|
|
|
|
Filters that are attached directly to a parent are executed before any filters
|
|
attached to the cut.
|
|
|
|
|
|
Tractor
|
|
-------
|
|
|
|
A tractor is an object that allows the manipulation of multiple video and audio
|
|
tracks.
|
|
|
|
Stepping away from the player example we've been tinkering with for a minute,
|
|
let's assume we want to do something like dub a video with some audio. This
|
|
a very trivial thing to do:
|
|
|
|
Tractor *dub( char *video_file, char *audio_file )
|
|
{
|
|
Tractor *tractor = new Tractor( );
|
|
Producer video( video_file );
|
|
Producer audio( audio_file );
|
|
tractor->set_track( video, 0 );
|
|
tractor->set_track( audio, 1 );
|
|
return tractor;
|
|
}
|
|
|
|
That's all that needs to be done - you can now connect the returned object to a
|
|
consumer, or add it to a playlist, or even apply it as a track to another tractor.
|
|
|
|
|
|
Transition
|
|
----------
|
|
|
|
Let's now assume we want to mix the audio between two tracks - to do this, we
|
|
need to introduce the concept of a transition. A transition in mlt is a service
|
|
which combines frames from two producers to produce a new frame.
|
|
|
|
Tractor *mix( char *video_file, char *audio_file )
|
|
{
|
|
Tractor *tractor = new Tractor( );
|
|
Transition mix( "mix" );
|
|
Producer video( video_file );
|
|
Producer audio( audio_file );
|
|
tractor.set_track( video, 0 );
|
|
tractor.set_track( audio, 1 );
|
|
tractor.field.plant_transition( mix, 0, 1 );
|
|
return tractor;
|
|
}
|
|
|
|
The tractor returned will now mix the audio from the original video and the
|
|
audio.
|
|
|
|
|
|
Mix
|
|
---
|
|
|
|
There is a convenience function which simplifies the process of applying
|
|
transitions betwee adjacent cuts on a playlist. This is often preferable
|
|
to use over the constuction of your own tractor and transition set up.
|
|
|
|
To apply a 25 frame luma transition between the first and second cut on
|
|
the playlist, you could use:
|
|
|
|
Transition luma;
|
|
playlist.mix( 0, 25, luma );
|
|
|
|
|
|
Events
|
|
------
|
|
|
|
Typically, applications need to be informed when changes occur in an mlt++ object.
|
|
This facilitates application services such as undo/redo management, or project
|
|
rendering in a timeline type widget and many other types of operations which an
|
|
application needs.
|
|
|
|
As an example, consider the following:
|
|
|
|
class Westley
|
|
{
|
|
private:
|
|
Consumer consumer;
|
|
Tractor &tractor;
|
|
public:
|
|
Westley( MltTractor &tractor ) :
|
|
tractor( tractor ),
|
|
consumer( "westley" )
|
|
{
|
|
consumer.connect( tractor );
|
|
tractor.listen( tractor, "producer-changed",
|
|
( mlt_listener )Westley::listener );
|
|
}
|
|
|
|
static void listener( Properties *tractor, Westley *object )
|
|
{
|
|
object->activate( );
|
|
}
|
|
|
|
void activate( )
|
|
{
|
|
consumer.start( );
|
|
}
|
|
};
|
|
|
|
Now, each time the tractor is changed, the westley representation is output to
|
|
stderr.
|
|
|
|
|
|
Servers and Westley Docs
|
|
------------------------
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
A server process would be running as follows:
|
|
|
|
#include <mlt++/Miracle>
|
|
using namespace Mlt;
|
|
|
|
int main( void )
|
|
{
|
|
Miracle miracle( "miracle", 5250 );
|
|
miracle.start( );
|
|
miracle.execute( "uadd sdl" );
|
|
miracle.execute( "play u0" );
|
|
miracle.wait_for_shutdown( );
|
|
return 0;
|
|
}
|
|
|
|
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:
|
|
|
|
Conumser valerie( "valerie", "localhost:5250" );
|
|
valerie.connect( producer );
|
|
valerie.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.
|
|
|
|
|
|
That's All Folks...
|
|
-------------------
|
|
|
|
And that, believe it or not, is a fairly complete summary of the classes you'll
|
|
typically be interfacing with in mlt++. Obviously, there's a little more to it
|
|
than this - a couple of intrisinc classes have been glossed over (notably, the
|
|
Properties and Service base classes). The next section will cover all of the
|
|
above, but in much more detail...
|
|
|
|
|
|
DIGGING DEEPER
|
|
--------------
|
|
|
|
The previous section was designed to give you a whistle stop tour through the major
|
|
framework classes. This section will take you through the scenic route.
|
|
|
|
|
|
Introducing Base Classes
|
|
------------------------
|
|
|
|
Services in mlt are the collective noun for Producers, Filters, Transitions and
|
|
Consumer. A Service is also the base class from which all of these classes
|
|
extend. It provides the basic connectivity which has been shown throughout the
|
|
examples in the previous section.
|
|
|
|
Properties are the main way in which we communicate with the Services -
|
|
essentially, it provides get/set methods for named values. All services extend
|
|
Properties.
|
|
|
|
|
|
Properties
|
|
----------
|
|
|
|
Properties provide the general mechanism for communicating with Services -
|
|
through the Properties interface, we are able to manipulate and serialise
|
|
a services state.
|
|
|
|
For example, to dump all the properties to stdout, you can use something
|
|
like:
|
|
|
|
void dump( Properties &properties )
|
|
{
|
|
for ( int i = 0; i < properties.count( ); i ++ )
|
|
cout << Properties.get_name( i ) << " = " << Properties.get( i ) << endl;
|
|
}
|
|
|
|
Note that the properties object handles type conversion, so the following
|
|
is acceptable:
|
|
|
|
properties.set( "hello", "10.5" );
|
|
int hello_int = properties.get_int( "hello" );
|
|
double hello_double = properties.get_double( "hello" );
|
|
|
|
A couple of convenience methods are provide to examine or serialise property
|
|
objects.
|
|
|
|
For example:
|
|
|
|
properties.debug( );
|
|
|
|
will report all serialisable properties on stderr, in the form:
|
|
|
|
Object: [ ref=1, in=0, out=0, track=0, u=75, v=150, _unique_id=15,
|
|
mlt_type=filter, mlt_service=sepia ]
|
|
|
|
|
|
Services
|
|
--------
|
|
|
|
Typically, all the services are constructed via the specific classes
|
|
constructor. Often, you will receive Service objects rather than their
|
|
specific type. In order to access the extended classes interface,
|
|
you will need to create a reference.
|
|
|
|
For example, given an arbitrary Service object, you can determine its
|
|
type by using the type method - this will return a 'service_type' which
|
|
has values of producer_type, filter_type etc. Alternatively, you can
|
|
create a wrapping object and check on its validity.
|
|
|
|
bool do_we_have_a_producer( Service &service )
|
|
{
|
|
Producer producer( service );
|
|
return producer.is_valid( );
|
|
}
|
|
|
|
|
|
Events
|
|
------
|
|
|
|
|
|
Servers and Westley Docs
|
|
------------------------
|
|
|
|
For various reasons, you might want to serialise a producer to a string.
|
|
To do this, you just need to specify a property to write to:
|
|
|
|
Consumer westley( "westley", "buffer" );
|
|
westley.connect( producer );
|
|
westley.start( );
|
|
buffer = westley.get( "buffer" );
|
|
|
|
You can use any name you want, and you can change it using the "resource"
|
|
property. Any name with a '.' in it is considered to be a file. Hence, you
|
|
can use a westley consumer to store multiple instances of the same MLT
|
|
object - useful if you want to provide undo/redo capabilities in an
|
|
editing application.
|
|
|
|
Should you receive an xml document as a string, and you want to send it
|
|
on to a server, you can use:
|
|
|
|
Conumser valerie( "valerie", "localhost:5250" );
|
|
valerie.set( "westley", buffer );
|
|
valerie.start( );
|
|
|
|
If you need to obtain an MLT object from a string:
|
|
|
|
Producer producer( "westley-xml", buffer );
|
|
|
|
The following shows a working example of an extended server:
|
|
|
|
class ShotcutServer : public Miracle
|
|
{
|
|
public:
|
|
ShotcutServer( char *id, int port ) :
|
|
Miracle( id, port )
|
|
{
|
|
}
|
|
|
|
void set_receive_doc( bool doc )
|
|
{
|
|
set( "push-parser-off", doc );
|
|
}
|
|
|
|
// Reject all commands other than push/receive
|
|
Response *execute( char *command )
|
|
{
|
|
valerie_response response = valerie_response_init( );
|
|
valerie_response_set_error( response, 400, "Not OK" );
|
|
return new Response( response );
|
|
}
|
|
|
|
// Push document handler
|
|
Response *received( char *command, char *doc )
|
|
{
|
|
valerie_response response = valerie_response_init( );
|
|
// Use doc in some way and assign Response
|
|
if ( doc != NULL )
|
|
valerie_response_set_error( response, 200, "OK" );
|
|
return new Response( response );
|
|
}
|
|
|
|
// Push service handler
|
|
Response *push( char *command, Service *service )
|
|
{
|
|
valerie_response response = valerie_response_init( );
|
|
// Use service in some way and assign Response
|
|
if ( service != NULL )
|
|
valerie_response_set_error( response, 200, "OK" );
|
|
return new Response( response );
|
|
}
|
|
};
|
|
|
|
NB: Should you be incorporating this into a GUI application, remember that the
|
|
execute, received and push methods are invoked from a thread - make sure that
|
|
you honour the locking requirements of your GUI toolkit before interacting with
|
|
the UI.
|
|
|
|
|