This image can be a 16 bit PGM (grayscale bitmap) or the luma channel of
any video producer. A number of high quality wipes can be downloaded from
http://mlt.sf.net/. It also performs field rendering.
+ The second wipe demonstrates the ability to control the direction of the
+ wipe as well.
Obscure
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE westley [
+ <!ENTITY msg "Hello world!">
+]>
+<westley>
+ <producer id="producer0">
+ <property name="mlt_service">pango</property>
+ <property name="text" value="&msg;"/>
+ </producer>
+</westley>
Constructor Argument
- file - an XML text file containing westley XML (schema pending)
-
+ URL - an XML text file containing westley XML (schema/DTD pending)
+ - Since westley files can be parameterised, the URL syntax is:
+ {file-name}[?{param-name}{'='|':'}{param-value}[&{param-name}{'='|':'}{param-value}...]]
+ A colon is allowed instead of an equal sign to pacify inigo,
+ who tokenises anything with an equal sign as a property
+ setting. Also, when running inigo from the shell, beware of
+ the '?' and shell filename expansion. You can surround the URL
+ with single quotations to prevent expansion. Finally, fezzik
+ will fail to match the filename when you use parameters, so
+ preface the url with 'westley:' to force fezzik to load with
+ the westley service.
+
Read Only Properties
string resource - file location
double softness - only when using a luma map, how soft to make the
edges between A and B. 0.0 = no softness. 1.0 =
too soft.
+ int reverse - reverse the direction of the transition.
Any property starting with "producer." is passed to the non-PGM luma
producer.
Note that it's only applied to the visible parts of the top track.
- The requirement to apply a filter to the output, as opposed to a specific track
- leads us to the final item in the Rules section above. As an example, let's
- assume we wish to watermark all output, then we could use the following:
+ The requirement to apply a filter to the output, as opposed to a specific
+ track leads us to the final item in the Rules section above. As an example,
+ let's assume we wish to watermark all output, then we could use the
+ following:
<westley>
<producer id="producer0">
be better handled at the playout stage itself (ie: as a filter automatically
placed between all producers and the consumer).
- TODO: transition example
+ Tracks act like "layers" in an image processing program like the GIMP. The
+ bottom-most track takes highest priority and higher layers are overlays
+ and do not appear unless there are gaps in the lower layers or unless
+ a transition is applied that merges the tracks on the specifed region.
+ Practically speaking, for A/B video editing it does not mean too much,
+ and it will work as expected; however, as a general rule apply any CGI
+ (graphic overlays with pixbuf or titles with pango) on tracks higher than
+ your video tracks. Also, this means that any audio-only tracks that are
+ lower than your video tracks will play rather than the audio from the video
+ clip. Remember, nothing is affected like mixing or compositing until one
+ applies a transition or appropriate filter.
+
+ <westley>
+ <producer id="producer0">
+ <property name="resource">clip1.dv</property>
+ </producer>
+ <playlist id="playlist0">
+ <entry producer="producer0"/>
+ </playlist>
+ <producer id="producer1">
+ <property name="resource">clip2.mpeg</property>
+ </producer>
+ <playlist id="playlist1">
+ <blank length="50"/>
+ <entry producer="producer1"/>
+ </playlist>
+ <tractor id="tractor0" in="0" out="315">
+ <multitrack id="multitrack0">
+ <track producer="playlist0"/>
+ <track producer="playlist1"/>
+ </multitrack>
+ <transition id="transition0" in="50" out="74">
+ <property name="a_track">0</property>
+ <property name="b_track">1</property>
+ <property name="mlt_service">luma</property>
+ </transition>
+ <transition id="transition1" in="50" out="74">
+ <property name="a_track">0</property>
+ <property name="b_track">1</property>
+ <property name="mlt_service">mix</property>
+ <property name="start">0.0</property>
+ <property name="end">1.0</property>
+ </transition>
+ </tractor>
+ </westley>
+ A "luma" transition is a video wipe processor that takes a greyscale bitmap
+ for the wipe definition. When one does not specify a bitmap, luma performs
+ a dissolve. The "mix" transition does an audio mix, but it interpolates
+ between the gain scaling factors between the start and end properties -
+ in this example, from 0.0 (none of track B) to 1.0 (all of track B).
+ Because the bottom track starts out with a gap specified using the <blank>
+ element, the upper track appears during the blank segment. See the demos and
+ services.txt to get an idea of the capabilities of the included transitions.
Flexibility:
any embedded XML that contains an element named "property" because
westley collects embedded XML until it reaches a closing property tag.
- TODO: xml entities
-
+
+Entities and Parameterisation:
+
+ The westley producer parser supports XML entities. An example:
+
+ <?xml version="1.0"?>
+ <!DOCTYPE westley [
+ <!ENTITY msg "Hello world!">
+ ]>
+ <westley>
+ <producer id="producer0">
+ <property name="mlt_service">pango</property>
+ <property name="text">&msg;</property>
+ </producer>
+ </westley>
+
+ If you are embedding another XML document into a property value not using
+ a CNODE section, then any DOCTYPE section must be relocated before any of
+ the xml elements to be well-formed. See demo/dvg.westley for an example.
+
+ Entities can be used to parameterise westley! Using the above example, the
+ entity declared serves as the default value for &msg;. The entity content
+ can be overridden from the resource property supplied to the westley
+ producer. The syntax is the familiar, url-encoded query string used with
+ HTTP, e.g.: file?name=value&name=value...
+
+ There are a couple of rules of usage. The Miracle LOAD command and inigo
+ command line tool require you to preface the URL with "westley:" because
+ the query string destroys the filename extension matching peformed by
+ Fezzik. Also, inigo looks for '=' to tokenise property settings. Therefore,
+ one uses ':' between name and value instead of '='. Finally, since inigo
+ is run from the shell, one must enclose the URL within single quotes to
+ prevent shell filename expansion, or similar.
+
+ Needless to say, the ability to parameterise westley XML compositions is
+ an extremely powerful tool. The above example is avialable in
+ demo/entity.westley for you to try out. Override the message from inigo:
+ inigo 'westley:entity.westley?msg:Amazing!'
+
+ Technically, the entity declaration is not needed in the head of the XML
+ document if you always supply the parameter. However, you run the risk
+ of unpredictable behviour without one. Therefore, it is safest and a best
+ practice to always supply an entity declaration. It is improves the
+ readability as one does not need to search for the entity references to
+ see what parameters are available.
+
-Technique:
+Tips and Technique:
If one finds the above hierarchical, abbreviated format intuitive,
start with a simple template and fill and extend as needed:
</entry>
</playlist>
+ If you end up making a collection of templates for various situations, then
+ consider using XML Entities to make the template more effective by moving
+ anything that should parameterised into an entity.
+
If you want to have a silent, black background for audio and video fades,
- then make the last track simply <producer mlt_service="colour"/>. Then,
- use composite and volume key-framable properties.
- TODO: to be continued
+ then make the top track simply <producer mlt_service="colour"/>. Then,
+ use composite and volume effects. See the "Fade from/to black/silence"
+ demo for an example (demo/mlt_fade_black).
- TODO: considerations with mixing multiple audio layers
+ If you apply the reverse=1 property to a transition like "luma," then
+ be careful because it also inherently swaps the roles of A and B tracks.
+ Therefore, you need to might need to swap the a_track and b_track values
+ if it did not turn out the way you expected. See the "Clock in and out"
+ for an example (demo/mlt_clock_in_and_out).
char *filename = (char*) cmd_arg->argument;
char fullname[1024];
int flush = 1;
+ char *service;
if ( filename[0] == '!' )
{
filename ++;
}
- if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' )
- filename++;
+ service = strchr( filename, ':' );
+ if ( service != NULL )
+ {
+ service = filename;
+ filename = strchr( service, ':' );
+ *filename ++ = '\0';
+
+ if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' )
+ filename++;
+
+ snprintf( fullname, 1023, "%s:%s%s", service, cmd_arg->root_dir, filename );
+ }
+ else
+ {
+ if ( strlen( cmd_arg->root_dir ) && filename[0] == '/' )
+ filename++;
- snprintf( fullname, 1023, "%s%s", cmd_arg->root_dir, filename );
+ snprintf( fullname, 1023, "%s%s", cmd_arg->root_dir, filename );
+ }
if (unit == NULL)
return RESPONSE_INVALID_UNIT;
// TODO: destroy unreferenced producers (they are currently destroyed
// when the returned producer is closed).
-// TODO: determine why deserialise_context can not be released.
#include "producer_westley.h"
#include <framework/mlt.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
+#include <ctype.h>
#include <libxml/parser.h>
#include <libxml/parserInternals.h> // for xmlCreateFileParserCtxt
xmlDocPtr entity_doc;
int depth;
int branch[ STACK_SIZE ];
+ const xmlChar *publicId;
+ const xmlChar *systemId;
+ mlt_properties params;
};
typedef struct deserialise_context_s *deserialise_context;
free( value);
}
+/** Convert parameters parsed from resource into entity declarations.
+*/
+static void params_to_entities( deserialise_context context )
+{
+ if ( context->params != NULL )
+ {
+ int i;
+
+ // Add our params as entitiy declarations
+ for ( i = 0; i < mlt_properties_count( context->params ); i++ )
+ {
+ xmlChar *name = ( xmlChar* )mlt_properties_get_name( context->params, i );
+ xmlAddDocEntity( context->entity_doc, name, XML_INTERNAL_GENERAL_ENTITY,
+ context->publicId, context->systemId, ( xmlChar* )mlt_properties_get( context->params, name ) );
+ }
+
+ // Flag completion
+ mlt_properties_close( context->params );
+ context->params = NULL;
+ }
+}
+
// The following 3 facilitate entity substitution in the SAX parser
static void on_internal_subset( void *ctx, const xmlChar* name,
const xmlChar* publicId, const xmlChar* systemId )
struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
deserialise_context context = ( deserialise_context )( xmlcontext->_private );
+ context->publicId = publicId;
+ context->systemId = systemId;
xmlCreateIntSubset( context->entity_doc, name, publicId, systemId );
+
+ // Override default entities with our parameters
+ params_to_entities( context );
}
static void on_entity_declaration( void *ctx, const xmlChar* name, int type,
struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
deserialise_context context = ( deserialise_context )( xmlcontext->_private );
+ // Setup for entity declarations if not ready
+ if ( xmlGetIntSubset( context->entity_doc ) == NULL )
+ {
+ xmlCreateIntSubset( context->entity_doc, "westley", "", "" );
+ context->publicId = "";
+ context->systemId = "";
+ }
+
+ // Add our parameters if not already
+ params_to_entities( context );
+
return xmlGetDocEntity( context->entity_doc, name );
}
+/** Convert a hexadecimal character to its value.
+*/
+static int tohex( char p )
+{
+ return isdigit( p ) ? p - '0' : tolower( p ) - 'a' + 10;
+}
-mlt_producer producer_westley_init( char *filename )
+/** Decode a url-encoded string containing hexadecimal character sequences.
+*/
+static char *url_decode( char *dest, char *src )
+{
+ char *p = dest;
+
+ while ( *src )
+ {
+ if ( *src == '%' )
+ {
+ *p ++ = ( tohex( *( src + 1 ) ) << 4 ) | tohex( *( src + 2 ) );
+ src += 3;
+ }
+ else
+ {
+ *p ++ = *src ++;
+ }
+ }
+
+ *p = *src;
+ return dest;
+}
+
+/** Extract the filename from a URL attaching parameters to a properties list.
+*/
+static void parse_url( mlt_properties properties, char *url )
+{
+ int i;
+ int n = strlen( url );
+ char *name = NULL;
+ char *value = NULL;
+
+ for ( i = 0; i < n; i++ )
+ {
+ switch ( url[ i ] )
+ {
+ case '?':
+ url[ i++ ] = '\0';
+ name = &url[ i ];
+ break;
+
+ case ':':
+ case '=':
+ url[ i++ ] = '\0';
+ value = &url[ i ];
+ break;
+
+ case '&':
+ url[ i++ ] = '\0';
+ if ( name != NULL && value != NULL )
+ mlt_properties_set( properties, name, value );
+ name = &url[ i ];
+ value = NULL;
+ break;
+ }
+ }
+ if ( name != NULL && value != NULL )
+ mlt_properties_set( properties, name, value );
+}
+
+mlt_producer producer_westley_init( char *url )
{
xmlSAXHandler *sax = calloc( 1, sizeof( xmlSAXHandler ) );
struct deserialise_context_s *context = calloc( 1, sizeof( struct deserialise_context_s ) );
int i = 0;
struct _xmlParserCtxt *xmlcontext;
int well_formed = 0;
+ char *filename = strdup( url );
context->producer_map = mlt_properties_new();
context->destructors = mlt_properties_new();
+ context->params = mlt_properties_new();
+
+ // Decode URL and parse parameters
+ parse_url( context->params, url_decode( filename, url ) );
// We need to track the number of registered filters
mlt_properties_set_int( context->destructors, "registered", 0 );
mlt_properties_set_data( properties, "__destructors__", context->destructors, 0, (mlt_destructor) mlt_properties_close, NULL );
// Now assign additional properties
- mlt_properties_set( properties, "resource", filename );
+ mlt_properties_set( properties, "resource", url );
// This tells consumer_westley not to deep copy
mlt_properties_set( properties, "westley", "was here" );
mlt_properties_close( context->destructors );
}
- free( context->stack_service );
+ // Clean up
mlt_properties_close( context->producer_map );
- //free( context );
+ if ( context->params != NULL )
+ mlt_properties_close( context->params );
+ free( context );
+ free( filename );
return MLT_PRODUCER( service );
}