Wednesday, October 04, 2006

Well, Seo Sanghyeon asked me a pretty reasonable question of the last entry

"Why don't you use help(), instead of printing Overloads?"

Good question, I didn't realise that actually worked in IronPython for .Net types, I was very pleasantly surprised, I remember in the early betas i tried using help() on various managed types and it would just raise a NotImplementedException exception (or something like that, I forget now) - so what does that mean for my examples... well... let's take a system type like Guid and use help() on it.

IronPython 1.0.60816 on .NET 2.0.50727.42
Copyright (c) Microsoft Corporation. All rights reserved.
>>> import System
>>> from System import *
>>> help(Guid)
Help on Guid in module System in mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

 |      Guid(Array[Byte] b)
 |      Guid(UInt32 a, UInt16 b, UInt16 c, Byte d, Byte e, Byte f, Byte g, Byteh, Byte i, Byte j, Byte k)
 |      Guid(str g)
 |      Guid(int a, Int16 b, Int16 c, Array[Byte] d)
 |      Guid(int a, Int16 b, Int16 c, Byte d, Byte e, Byte f, Byte g, Byte h, Byte i, Byte j, Byte k)
 |      Guid CreateInstance[Guid]()
 |
 |  Data and other attributes defined here:
 |
 |      CompareTo(...)
 |              int CompareTo(self, object value)
 |              int CompareTo(self, Guid value)
 |      Equals(...)
 |              bool Equals(self, object o)
 |              bool Equals(self, Guid g)
 |      Finalize(...)
 |              Finalize(self)
 |      GetHashCode(...)
 |              int GetHashCode(self)
 |      GetType(...)
 |              Type GetType(self)
 |      MemberwiseClone(...)
 |              object MemberwiseClone(self)
 |      NewGuid(...)
 |              Guid NewGuid()
 |      ToByteArray(...)
 |              Array[Byte] ToByteArray(self)
 |      ToString(...)
 |              str ToString(self)
 |              str ToString(self, str format)
 |              str ToString(self, str format, IFormatProvider provider)
 |      __eq__(...)
 |              bool op_Equality(Guid a, Guid b)
 |      __init__(...)
 |              x.__init__(...) initializes x; see x.__class__.__doc__ for signature
 |              x.__init__(...) initializes x; see x.__class__.__doc__ for signature
 |      __ne__(...)
 |              bool op_Inequality(Guid a, Guid b)
 |      __new__(...)
 |              __new__(cls, Array[Byte] b)
 |              __new__(cls, UInt32 a, UInt16 b, UInt16 c, Byte d, Byte e, Bytef, Byte g, Byte h, Byte i, Byte j, Byte k)
 |              __new__(cls, str g)
 |              __new__(cls, int a, Int16 b, Int16 c, Array[Byte] d)
 |              __new__(cls, int a, Int16 b, Int16 c, Byte d, Byte e, Byte f, Byte g, Byte h, Byte i, Byte j, Byte k)
 |              Guid CreateInstance[Guid]()
 |

>>>

Sweet, it works a treat - and will save a lot of digging time - though you might want to capture the results for big classes ;o)

Thanks Seo!

(btw, Seo is the man behind the Iron Python Community Edition & FePy projects - which can be found on sourceforge) and I would have to agree with this article that he does seem almost OmniPresent in the IronPython community...
posted @ Wednesday, October 04, 2006 12:20:53 AM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | Trackback |

This entry was going to cover thinking about your .Net classes from the perspective of Python consumption… but actually it's not, we'll cover that another time.

What this post is really going to be about is how to spelunk a little into our managed classes via the IronPython interactive console - and to see how some common practices in .Net have interesting results in python.

This entry is going to be a little slow paced, I’m making the assumption you’re a .Net developer, and haven’t cut Python code before, if you know your way around Python probably best to pass this one over.

Also worth noting at this point that I'm really just a Python hacker, and certainly no guru...

So first off, we have a class with a bunch of overloads for various methods, here is the interface… I’ve left the code for the class itself out, but the class is called MyFileStore.

public interface IMyFileStore

{

    Guid AddFile(string fileName);

    Guid AddFile(string fileName, string mimeType);

    Guid AddFile(byte[] contents, string mimeType);

    Guid[] AddFiles(params string[] fileNames);

    Guid AddFile(byte[] contents);

    byte[] ReadFile(Guid id, out string mimeType);

    void ReadFile(Guid id, string fileName);

    void ExtractFiles(ref IList<byte[]> myList);

}

Pretty simple, now lets look at using this class with Iron python, I’ll be running with the ipy (the interactive interpreter) so we can investigate our types as we go along (this is an executable which comes with the Iron Python binaries). The ipy console has a prompt consisting of three greater then signs “>>>” so you’ll know what I’m typing into the console by looking for those.

First off, we need to load our type… this is pretty trivial…

>>> import clr
>>> clr.AddReferenceToFileAndPath("D:\\Demo\\SampleIronPythonLib.dll")
>>> from IronPythonLib import *

All done, lets make sure our class is in there, using the inbuilt function dir() – a list of built-in functions can be found here: http://www.python.net/quick-ref1_52.html#BuiltIn

>>> dir()
['IMyFileStore', 'MyFileStore', '_', '__builtins__', '__doc__', '__name__', 'clr', 'results', 'site', 'store', 'sys']

MyFileStore, sweet.

First off, lets create an instance of my type (not yours ;o)

>>> store = new MyFileStore()
Traceback (most recent call last):
SyntaxError: unexpected token MyFileStore (<stdin>, line 1)
>>>

Arse, python doesn’t have a new operator… forgot that, lets try it again.

>>> store = MyFileStore

Right, looks good, interpreter didn’t return any errors… but wait, lets make sure we got what we want, which is an instance of MyFileStore….

>>> store
<type ‘MyFileStore’>
>>>

Woops, that’s not what we wanted, we want an instance, not the type… lets try again:

>>> store = MyFileStore()
>>> store
<MyFileStore object at 0x0000000000000035>
>>>

That’s better, lets find out what our store is capable off, using the dir command.

>>> dir(store)
['AddFile', 'AddFiles', 'Equals', 'ExtractFiles', 'Finalize', 'GetHashCode', 'GetType', 'MakeDynamicType', 'MemberwiseClone', 'ReadFile', 'Reduce', 'ReferenceEquals', 'ToString', '__class__', '__doc__', '__init__', '__module__', '__new__','__reduce__', '__reduce_ex__', '__repr__']
>>>

We see our AddFile, AddFiles,ReadFile & ExtractFiles methods – everything looks to be in order… but what are the parameters for those methods?

>>> dir(store.AddFile)
['Call', 'CallInstance', 'Equals', 'Finalize', 'GetHashCode', 'GetTargetType', '
GetType', 'Make', 'MakeDelegate', 'MakeDynamicType', 'MemberwiseClone', 'Overloads', 'PrependInstance', 'Reduce', 'ReferenceEquals', 'ToString', '__call__', '__class__', '__doc__', '__eq__', '__getitem__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__str__']
>>>

Hmm… what’s the “Overloads” thing, lets find out…

>>> dir(store.AddFile.Overloads)
['Equals', 'Finalize', 'Function', 'GetHashCode', 'GetOverload', 'GetTargetFunction', 'GetType', 'MakeDynamicType', 'MemberwiseClone', 'Reduce', 'ReferenceEquals', 'Targets', 'ToString', '__class__', '__doc__', '__getitem__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__str__']
>>>

That doesn’t seem very useful, though what if we check it’s string representation?

>>> store.AddFile.Overloads
<IronPython.Runtime.Calls.BuiltinFunctionOverloadMapper object at 0x0000000000000036 [{'Guid AddFile(self, str fileName)': <built-in function AddFile>, 'Guid AddFile(self, str fileName, str mimeType)': <built-in function AddFile>, 'Guid AddFile(self, Array[Byte] contents, str mimeType)': <built-in function AddFile>, 'Guid AddFile(self, Array[Byte] contents)': <built-in function AddFile>}]>
>>>

Look at that, there are our 4 overloads – but it’s a bit jumbled, what am I looking at it….

Well at first glance it looks a bit like a python dictionary… notice the colon separating the signature for the method and <built-in function AddFile> … what if we try to grab one for use later, maybe for some functional programming, why don’t we get the one which takes the fileName and mimeType parameters eh?


>>> myFunc = store.AddFile.Overloads[str,str]
Traceback (most recent call last):
File , line 0, in <stdin>##100
TypeError: __getitem__() takes exactly 1 argument (1 given)
>>>

Bugger, that didn’t work… still, not a bad guess - one parameter eh? Lets have another stab, maybe it just wants us to group the types together somehow?

>>> myFunc = store.AddFile.Overloads[(str,str)]
>>> myFunc
<built-in method AddFile of MyFileStore object at 0x0000000000000035>
>>>

Looks better, of course if we wanted to we could use our function right now… let’s give it a whirl:

>>> myFunc("d:\\testinput.3gp", "video/3gpp")
<System.Guid object at 0x0000000000000037 [7b6d6dc5-bbe6-4831-bff7-21984031684f]>
>>>

Looks to have worked, the method has successfully returned us a Guid identifying the new file… shame we didn’t save it into a variable for future reference, cest la vie.

Luckily I can just copy and paste it – lets see if I can get the file back using this overload…

byte[] ReadFile(Guid id, out string mimeType);

Simple as.. lets try…

>>> id = Guid("7b6d6dc5-bbe6-4831-bff7-21984031684f")
Traceback (most recent call last):
File , line 0, in <stdin>##116
NameError: name 'Guid' not defined
>>>

Woops, we need to import the System namespace…

>>> import System

Now lets try again…

>>> id = Guid("7b6d6dc5-bbe6-4831-bff7-21984031684f")
Traceback (most recent call last):
File , line 0, in <stdin>##116
NameError: name 'Guid' not defined
>>>

Huh… oh wait, let’s check our local symbol table:

>>> dir()
['MyFileStore', 'System', '_', '__builtins__', '__doc__', '__name__', 'clr', 'myArray', 'myFunc', 'results', 'site', 'store', 'sys']

System eh? Lets dig into it…

>>> dir(System)
['AccessViolationException', 'Action', 'ActivationContext', 'Activator', 'AppDom
ain', 'AppDomainInitializer', 'AppDomainManager', 'AppDomainManagerInitializatio
nOptions', 'AppDomainSetup', 'AppDomainUnloadedException', 'ApplicationException… and all the rest.
>>

Well, what can we do about that… we could import them all using “from System import *” – but we only need Guid, so why don’t we just grab that eh?

>>> from System import Guid
>>> dir()
['Guid', 'MyFileStore', 'System', '_', '__builtins__', '__doc__', '__name__', 'clr', 'myArray', 'myFunc', 'results', 'site', 'store', 'sys']
>>>

Sweet, now lets try it again… finally!

>>> id = Guid("7b6d6dc5-bbe6-4831-bff7-21984031684f")
>>> mimeType = ""
>>> contents = store.ReadFile(id, mimeType)
Traceback (most recent call last):
File , line 0, in <stdin>##136
File , line 0, in ReadFile##66
File D:\dev\Projects\IronPythonDemo\IronPythonLib\MyClass.cs, line 70, in Read
File
File mscorlib, line unknown, in WriteAllBytes
File mscorlib, line unknown, in .ctor
File mscorlib, line unknown, in Init
ValueError: Empty path name is not legal.
>>>

Oh no! what went wrong??… what’s this about an empty path name, I was expecting to get some bytes back…. Hmmm, don’t panic, lets just do some digging…

>>> store.ReadFile.Overloads
<IronPython.Runtime.Calls.BuiltinFunctionOverloadMapper object at 0x0000000000000039 [{'(Array[Byte], str) ReadFile(self, Guid id)': <built-in function ReadFile>, 'ReadFile(self, Guid id, str fileName)': <built-in function ReadFile>}]>
>>>

Well there’s the problem, I’ve called the wrong version, but… that’s odd, there’s a ReadFile there I haven’t defined in my class… and one that’s missing… hmmm, time to file a bug report.

Or not, this is just where the Python and .Net world don’t see eye to eye – Python doesn’t know about out parameters as such, so it simulates the effect by altering the method’s signature – lets have a go at playing by IronPython's rules:

>>> contentsAndMimeType = store.ReadFile(id)
>>> len(contentsAndMimeType)
2
>>> type(contentsAndMimeType[0])
<type 'Array[Byte]'>
>>> type(contentsAndMimeType[1])
<type 'str'>
>>> contentsAndMimeType[1]
'video/3gpp'
>>>

So our “out string mimeType” parameter was returned by the method instead, as the second item in an array… hmm… interesting.

Well if python does this to out parameters, what does it do to ref parameters – lets try the extract files method to see what happens… this method is implemented to create a new list if we don’t supply a valid instance, but lets pass in a valid one first – I wont be caught out this time, so I’ll import the types I need first (List<T>):

>>> from System.Collections.Generic import *
>>> bytes = List<byte[]>()
Traceback (most recent call last):
SyntaxError: unexpected token ] (<stdin>, line 1)
>>>

Oh dear, looks like Python doesn’t use the <T> syntax… lets start digging again…

>>> dir(List)
['Add', 'AddRange', 'AsReadOnly', 'BinarySearch', 'Capacity', 'Clear', 'Contains
', 'ConvertAll', 'CopyTo', 'Count', 'Enumerator', 'Equals', 'Exists', 'Finalize'
, 'Find', 'FindAll', 'FindIndex', 'FindLast', 'FindLastIndex', 'ForEach', 'GetEn
umerator', 'GetHashCode', 'GetRange', 'GetType', 'IndexOf', 'Insert', 'InsertRange', 'LastIndexOf', 'MakeDynamicType', 'MemberwiseClone', 'Reduce', 'ReferenceEquals', 'Remove', 'RemoveAll', 'RemoveAt', 'RemoveRange', 'Reverse', 'Sort', 'ToArray', 'ToString', 'TrimExcess', 'TrueForAll', '__class__', '__doc__', '__getitem__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setitem__']
>>> List
<type 'List[T]'>
>>>

Hmm… nothing obvious there, but we could hazard a guess…

>>> List[str]
<type 'List[str]'>
>>>

Hmmm… so lets give our List of byte array another go…

>>> bytes = List[byte[]]()
Traceback (most recent call last):
SyntaxError: unexpected token ] (<stdin>, line 1)

Hmm… it doesn’t like byte, lets have a look…

>>> byte
Traceback (most recent call last):
File , line 0, in <stdin>##56
NameError: name 'byte' not defined
>>> Byte
<type 'Byte'>
>>>

Ahhh… I forgot that “byte” (lowercase) isn’t actually what the Clr calls it… hmm… we could try it again, but it wont work – simply because Byte[] doesn’t make sense (if we’re using square brackets to index into a generic type… ) so having another dig in the IronPython docs we see that we need to index to the type “Array” with the type of our array, given this knowledge we can get what we want…

>>> bytes = List[Array[Byte]]()
>>> bytes
<List[Array[Byte]] object at 0x000000000000002C>
>>>

Sweet, now to call that ExtractFiles method…

>>> store.ExtractFiles(bytes)
<List[Array[Byte]] object at 0x000000000000002C>
>>> bytes
<List[Array[Byte]] object at 0x000000000000002C>
>>> len(bytes)
8

Well it returned a list… but it did accept our reference parameter, and it has updated the collection we passed to it… but what if we had passed it a null instead?

>>> bytes = None
>>> store.ExtractFiles(bytes)
<List[Array[Byte]] object at 0x0000000000000030>
>>> len(bytes)
Traceback (most recent call last):
File , line 0, in <stdin>##83
File , line 0, in Length##75
TypeError: len() of unsized object of type <type 'NoneType'>
>>>

Hmmm… it returned a list, but it didn’t set the value in our variable "bytes" – this isn’t really ref like behavior… yet, if you think about it we can get almost the same thing by doing this:

>>> bytes = store.ExtractFiles(bytes)
>>> len(bytes)
8
>>>

Right, our journey has almost come to an end… looking at the interface we have a method declared like so:

Guid[] AddFiles(params string[] fileNames);

params is handy in .Net code – anything to save tedious typing of array declarations, lets see if we can do the same thing in python?

>>> store.AddFiles("d:\\testinput1.3gp", "d:\\testinput2.3gp")
System.Guid[](<System.Guid object at 0x0000000000000031 [d3e8f099-3005-461c-a63a-fdc69b2091ee]>, <System.Guid object at 0x0000000000000032 [d9d1fb25-fc7a-47a2-89ac-c11264bfa47f]>)
>>>

Sweet, that’s a pleasant surprise after that whole out and ref debacle, I was beginning to loosing hope!

But wait, why not revive our faith in Python a little more by looking at a few tricks…

>>> paramsDict = { "fileName" : "d:\\testinput.3gp", "mimeType" : "video/3gpp"}
>>> store.AddFile(**paramsDict)
<System.Guid object at 0x0000000000000033 [e07680d9-f544-4847-9a1e-d04a0ef137f7]>
>>>

What did we just do? Well… given a dictionary, where the keys are the parameter names, we used them as the parameters for one of our .Net methods using the double asterisk syntax – think of what you’d have to do to code this in .Net… reflection city ;o)

It doesn’t just have to be dictionaries… we can use arrays too…

>>> paramsArray = [ "d:\\testinput.3gp", "video/3gpp" ]
>>> store.AddFile(*paramsArray)
<System.Guid object at 0x0000000000000034 [85006c43-e6f5-4e90-b837-0868788cf453]>
>>>

Neat, of course, being a dynamic language and all, the selection of the appropriate overload is based on the types in the array or dictionary… it would be a nightmare to do both of these in .Net with reflection yourself.

Faith still not restored eh, what about creating a python class that wraps an existing (possibly sealed) .Net class, that forwards calls on unless we want to override the behavior…

>>> class MyFileStoreWrapper:
... def __init__(self, realStore):
... self.realStore = realStore
... def ExtractFiles(self, myList):
... raise Exception, "this method isn't allowed"
... def __getattr__(self, name):
... if name == "ExtractFiles": return ExtractFiles
... return eval("self.realStore."+name)
...
>>> store = MyFileStoreWrapper(MyFileStore())
>>> store.AddFile("d:\\testinput.3gp")
<System.Guid object at 0x000000000000002C [a2bf8747-cc71-4b79-8c02-0b61a87ff67f]
>
>>> store.ExtractFiles(None)
Traceback (most recent call last):
File , line 0, in <stdin>##52
File , line 0, in ExtractFiles
Exception: this method isn't allowed
>>>

ExtractFiles can’t be invoked on our wrapper, however our wrapper automagically responds for other methods like AddFile – sadly I’ve used an eval (which, rhymes with Evil - coincidence?) here because MyFileStore doesn’t expose a __getattr__ method (because it's a .Net classes, not a native Python one).

The nice thing now, is that given a native python class, we can do some things we aren’t allowed to do to the underlying .Net class – like adding new methods at run time.

>>> def ExtractMoreFiles(self, myList):
... raise Exception, 'not implemented yet'
...
>>> MyFileStoreWrapper.ExtractMoreFiles = ExtractMoreFiles
>>> store.ExtractMoreFiles(None)
Traceback (most recent call last):
File , line 0, in <stdin>##60
File , line 0, in ExtractMoreFiles
Exception: not implemented yet
>>>

I think it’s pretty cool, and though python's metaprogramming model isn’t quite up to ruby standards, it’s still pretty easy to dig down and create some pleasantly surprising results :) and if you have your thinking caps on you can probably see how this stuff would really help to bring a DSL to life for your applications special needs.

And that’s where I’m going to conclude this rambling post… ;o) Hopefully it'll spark some thoughts about what's possible with IronPython and allow you avoid some simple mistakes when using .Net classes in iron python.

posted @ Tuesday, October 03, 2006 9:46:42 PM (New Zealand Daylight Time, UTC+13:00)    Comments [2] | Trackback |
 Tuesday, October 03, 2006

Second Alpha

I released the second alpha of the Splicer library last night… you can grab it from the Splicer Codeplex site

Key changes are:
  • It’s been FxCop’d – which in turn has seen some naming issues, misspellings, security issues, incorrect disposal implementations etc being fixed.
  • Implemented some features:
    • Support for adding System.Drawing.Image’s to a track (using overloads of the AddImage(…) methods.
    • Support for shadow copying input files – this will take a copy of a clip file, and use that in the ITimeline, disposing of the copy when the clip is disposed – this is especially useful when dealing with source filters which don’t like reading the same file simultaneously.
    • AbstractProgressParticipant class which can be used to generate your own progress participants, 0 or more participants can be registered for each group.  This allows you to do things like perform screen captures on the output of the timeline.
    • Windows media renderer will now notify you when the selected rendering profile is unsuitable for your timeline.
  • Fixed some bugs
    • The clock is now correctly disabled for the filter graph when rendering to a file.  This will speed up rendering in many scenarios.
    • When supplying an audio encoder for either WAV or AVI output, the format settings for the encoder were not being applied properly (was using defaults, instead of setting the number of channels, khz & kbps).

Though there are also plenty more, I doubt anyone has actually been using this library so far (as it's really only useful in a rather select set of circumstances) - however just to offer some vague hope of encouragement I’ll include some code samples to get you started – as there is no documentation short of the 150 odd unit tests included with the project... and won't be for some time.

Example 1 

First off, lets imagine you have some web 2.0 website, and you encourage people to upload audio comments, instead of text, yet you don’t know what format they’re going to provide, and you want to keep things standard (say by outputting a certain windows media audio format) that eases pressure on your web server.

using (ITimeline timeline = new DefaultTimeline())

{

    IGroup audioGroup = timeline.AddAudioGroup();

    ITrack rootTrack = audioGroup.AddTrack();

    rootTrack.AddAudio("testinput.mp3");

 

    using (

        WindowsMediaRenderer renderer =

            new WindowsMediaRenderer(timeline, "output.wma", WindowsMediaProfiles.LowQualityAudio))

    {

        renderer.Render();

    }

}


Here we are creating a Timeline, this is a container for all the audio and video compositing we are doing, next we create a group for our audio, a group is a top level composition… we then add a track to our audio group, which is a container for our audio clip we wish to add (in this case it’s an mp3, but it could be any audio format that you have a codec installed for).

Once your Timeline is ready to be rendered, we create a renderer, in this case it’s a windows media renderer, which takes your timeline, the name of the output file and the windows media profile we wish to use – profiles are xml strings containing the settings for the windows media encoder – A couple of bundled ones are included, though you would probably want to create your own using the “Windows Media Profile Editor” which is included with Windows Media Encoder 9.

Last of all we invoke the Renderer, generally rendering can take a little while, depening on the size of the content, and the complexity of the encoding process - so I would suggest invoking the Renderer asynchronously (all Renderers support the BeginRender(…) … EndRender(…) methods which should make this trivial).  Our examples will use the blocking Render() method, just because it's more concise.

Example 2

So audio cool, what about video?

Much the same:

using (ITimeline timeline = new DefaultTimeline())

{

    timeline.AddAudioGroup().AddTrack();

    timeline.AddVideoGroup(24, 320, 240).AddTrack();

    timeline.AddVideoWithAudio("input.avi");

 

    using (

        WindowsMediaRenderer renderer =

            new WindowsMediaRenderer(timeline, "output.wmv", WindowsMediaProfiles.LowQualityVideo))

    {

        renderer.Render();

    }

}



This time we create two groups, and two tracks, one of each for the audio from our source file, and another of each for the video.

Note we’ve specified the format of the video a little – so it will be 24 bits per pixel, and 320 high by 240 wide.
 
In this case we use a helpful method on the timeline itself which will Add a video clip to the video group and an audio clip to the audio group at the same time, called AddVideoWithAudio… Again we render it to windows media format, but this time we use a profile which has settings for both audio and video.

The bits per pixel becomes important when using something transitions or effects, as you need an alpha channel for them to work - this is generally achieved but just bumping the BPP from 24 to 32.

Example 3

Right, moving on from here, lets cover some more interesting ideas – first off, lets build a slide show video…

using (ITimeline timeline = new DefaultTimeline(25))

{

    IGroup group = timeline.AddVideoGroup(32, 160, 100);

 

    ITrack videoTrack = group.AddTrack();

    IClip clip1 = videoTrack.AddImage("image1.jpg", 0, 2);

    IClip clip2 = videoTrack.AddImage("image2.jpg", 0, 2);

    IClip clip3 = videoTrack.AddImage("image3.jpg", 0, 2);

    IClip clip4 = videoTrack.AddImage("image4.jpg", 0, 2);

 

    double halfDuration = 0.5;

 

    group.AddTransition(clip2.Offset - halfDuration, halfDuration, StandardTransitions.CreateFade(), true);

    group.AddTransition(clip2.Offset, halfDuration, StandardTransitions.CreateFade(), false);

 

    group.AddTransition(clip3.Offset - halfDuration, halfDuration, StandardTransitions.CreateFade(), true);

    group.AddTransition(clip3.Offset, halfDuration, StandardTransitions.CreateFade(), false);

 

    group.AddTransition(clip4.Offset - halfDuration, halfDuration, StandardTransitions.CreateFade(), true);

    group.AddTransition(clip4.Offset, halfDuration, StandardTransitions.CreateFade(), false);

 

    ITrack audioTrack = timeline.AddAudioGroup().AddTrack();

 

    IClip audio =

        audioTrack.AddAudio("soundtrack.wav", 0, videoTrack.Duration);

 

    audioTrack.AddEffect(0, audio.Duration,

                        StandardEffects.CreateAudioEnvelope(1.0, 1.0, 1.0, audio.Duration));

 

    using (

        WindowsMediaRenderer renderer =

            new WindowsMediaRenderer(timeline, "output.wmv", WindowsMediaProfiles.HighQualityVideo))

    {

        renderer.Render();

    }

}



In this case we are producing a slideshow – Note that 25 in the constructor of the timline, this is setting the frames per second, in this case we are saying 25fps…  The renderer has the final say in the “rendered” frames per second, based on it’s settings… but some renderers will use the frame rate they receive, such as the windows renderer (which renders the video into a window on-screen)

The slideshow fades between 4 still images, each lasts for a duration of 2 seconds, we add in a couple of transitions, fading out of one clip and into another – last of all we are using an AudioMixer effect on the audio track to create an Audio envelope, which means it will take one second for the audio to fade in from 0.0 volume to 1.0 volume (0 to 100%) and then play until one second before the end of the clip, at which point it will fade out from 100% volume to 0% volume.

Example 4

Last of all, you can capture rendered frames from a video, this might be handy to display on a web page, with links to the various quality formats beneath – you can grab these frames during encoding, or you can render to “null”, which allows you the grab frames, but wont produce any output video or audio file… Lets take a look at the second idea:

using (DefaultTimeline timeline = new DefaultTimeline())

{

    timeline.AddVideoGroup(24, 320, 240).AddTrack(); // we want 320x240 24bpp sized images

    timeline.AddVideo("transitions.wmv"); // 8 second video clip

 

    ImagesToDiskParticipant participant = new ImagesToDiskParticipant(24, 320, 240, Environment.CurrentDirectory, 1, 2, 3, 4, 5, 6, 7);

 

    using (NullRenderer render = new NullRenderer(timeline, null, new ICallbackParticipant[] { participant }))

    {

        render.Render();

    }

}


Here we add a video group (we don’t need any audio, we’re not capturing sound samples) and then create an ImageToDiskParticipant (this is just an example class, you would probably want to implement your own derived from the AbstractProgressParticipant class).  In this case the format for the video group will be what you end up with, our ImagesToDiskParticipant implements a simple queue where it picks out the nearest frame to the specified times.. so in this case transitions.wmv is an 8 second video clip, and we will take frame "snapshots" at 1,2,3,4,5,6 and 7 seconds aproximately. 

To make use of our participant we use one of the overloads of NullRenderer, and supply it a single-element array containing the image to disk participant.

The end result is 7 jpeg images on disk (frame0.jpg -> frame6.jpg)

Hopefully this has given you some ideas about how Splicer can be used for dealing width audio and video, next time I do an alpha drop I might include some samples which use different container formats (WAV & AVI) - or maybe I'll use splicer as an example of just how not to write an API you wish to expose to IronPython due to it's bloated number of overloads.
posted @ Monday, October 02, 2006 9:34:03 PM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | Trackback |
 Sunday, October 01, 2006

    [Test]

    public void ExtendingClass()

    {

        PythonEngine engine = new PythonEngine();

 

        engine.Import("clr");

        engine.LoadAssembly(GetType().Assembly);

        engine.ExecuteToConsole("from IronPythonLib.Tests import *");

 

        engine.Execute(@"class MyTransformer(IStringTransformer):       

def Transform(self, input):

    return input + "" is now transformed""");

 

        IStringTransformer transformer = engine.EvaluateAs<IStringTransformer>("MyTransformer()");

        Assert.AreEqual("input is now transformed", transformer.Transform("input"));

    }


hmmm?

edit: seems better now, swapped to a simpler layout though I doub't it will work well for people with lower resolutions... most people will (hopefully) read this via RSS at any rate.
posted @ Sunday, October 01, 2006 10:11:00 AM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | Trackback |

Back once more...

I decided to spend ten minutes implementing a version of the last post which didn't require us to generate a function definition for a python method with a generic number of parameters... it's actually not too hard.

First off, the key to solving this problem is to generate a Type for our custom delegate - for performance reasons you would want to cache the results, but we won't do that here... Also I think it would be more apropriate for the first 0->9 parameters that we just return a generic Func delegate (generic Func & Proc delegates are going to be part of .Net 3.0, as mentioned here - At the moment I use something similar from the RhinoCommons library, have a look here)

So, first off we build a helper method for generating our delegate - just heavy use of emit here, based off an example on MSDN:


public Type CreateCustomDelegate(Type returnType, params Type[] parameterTypes)

{

    AssemblyName assembly = new AssemblyName();

    assembly.Name = "CreateCustomDelegateAssembly";

 

    AssemblyBuilder assemblyBuilder =

        AppDomain.CurrentDomain.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.RunAndSave);

 

    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("TempModule");

 

    TypeBuilder typeBuilder =

        moduleBuilder.DefineType("TempDelegateType",

                                TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class |

                                TypeAttributes.AnsiClass | TypeAttributes.AutoClass, typeof (MulticastDelegate));

 

    ConstructorBuilder constructorBuilder =

        typeBuilder.DefineConstructor(

            MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public,

            CallingConventions.Standard, new Type[] {typeof (object), typeof (int)});

    constructorBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);

 

    MethodBuilder methodBuilder =

        typeBuilder.DefineMethod("Invoke",

                                MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot |

                                MethodAttributes.Virtual, returnType, parameterTypes);

    methodBuilder.SetImplementationFlags(MethodImplAttributes.Managed | MethodImplAttributes.Runtime);

 

    return typeBuilder.CreateType();

}


Basically it goes through the motions of building a delegate type, derived from MulticastDelegate with our selected output type and parameter types...

Now to put that type to use:


[Test]

public void WithCustomDelegate()

{

    Dictionary<string, object> context = new Dictionary<string, object>();

    context.Add("user", "alex");

    context.Add("age", 26);

 

    Proc testCore = delegate

        {

            List<string> parameters = new List<string>(context.Keys);

            List<object> values = new List<object>(context.Values);

 

            PythonEngine engine = new PythonEngine();

 

            Type[] parameterTypes = new Type[context.Count];

            for (int i = 0; i < parameterTypes.Length; i++) parameterTypes[i] = typeof (object);

 

            Type delegateType = CreateCustomDelegate(typeof (object), parameterTypes);

 

            Type pythonEngine = typeof (PythonEngine);

            MethodInfo genericMethod =

                pythonEngine.GetMethod("CreateMethod", new Type[] {typeof (string), typeof (IList<string>)});

            MethodInfo method = genericMethod.MakeGenericMethod(delegateType);

 

            object result =

                method.Invoke(engine,

                              new object[] {"return user + ' is ' + str(age) + ' years old'", parameters});

 

            Delegate myDelegate = (Delegate) result;

            Assert.AreEqual("alex is 26 years old", myDelegate.DynamicInvoke(values.ToArray()));

        };

 

    // try it with our two keys in the dictionary

 

    testCore();

 

    // try it with another key in the dictionary

 

    context.Add("monkey_colour", "brown");

    testCore();

}


Our test creates our custom delegate type for the number of parameters we have, and then we use that when invoking the PythonEngine.CreateMethod<TDelegate>(string statements, IList<string> parameters) method.

Here we have to do a little reflection, simply because we cant directly use a local variable of type "Type" as a generic type parameter, so we grab the generic method, set the parameter and then invoke it.

However in the end, as you can see we get a real Delegate, which we can use DynamicInvoke upon.

This is actually pretty handy, and it's certainly a lot more script-friendly, compared to the alternative of forcing users to look up values from context by index ie.

   
context['user'] + ' is ' + str(context['age']) + ' years old'

vs.

   
user + ' is ' + str(age) + ' years old'

I know what I prefer ;o)

posted @ Sunday, October 01, 2006 8:03:41 AM (New Zealand Daylight Time, UTC+13:00)    Comments [2] | Trackback |
 Saturday, September 30, 2006

Part  2 - More IronPython and delegates…


So we have briefly covererd strongly typed delegates and event handlers... But they make the assumption that we know what our arguments are at compile time, what if we dont?

Say for instance you're expression engine has a bag of contextual values being passed around in a dictionary, like this:

Dictionary<string, object> context = new Dictionary<string, object>();

context.Add("user", "alex");

context.Add("age", 26);


What if we want to use python to evaluate expressions against that context?... Say something like writing out “my name is alex and my age is 25" - the expression in python is easy enough, we can go:

    ‘my name is’ + name + ‘ and my age is ‘ + str(age)

But how do we marry all these together... lets start exploring... ever noticed that delegates have a DynamicInvoke method with a signature like this?

    object
DynamicInvoke(params object[] parameters)

Perhaps we can try and use this to our advantage... lets see shall we?

First Attempt

[Test]

[ExpectedException(typeof (ArgumentException), "T must be a concrete delegate, not MulticastDelegate or Delegate")]

public void LooseDelegateAndDynamicInvoke()

{

    PythonEngine engine = new PythonEngine();

 

    List<string> parameters = new List<string>(new string[] {"name", "age"});

 

    Delegate func =

        engine.CreateMethod<Delegate>("return ‘my name is’ + name + ‘ and my age is ‘ + str(age)", parameters);

    string result = (string) func.DynamicInvoke("alex", 26);

 

    Assert.AreEqual("my name is alex and my age is 25", result);

}


Sadly this doesn’t work so well, seems the PythonEngine is looking for a concrete delegate... we could try giving it void delegate, but it does type checking on the number of parameters and their type, so we're a little stuck.

At this point I think the best answer is to actually build some code which generates the apropriate delegate type, based on the supplied dictionary, and then passing that onto the python engine, but there is another way… it’s a little less elegant, but it’s at least amusing:

Round 2

First of let’s build a little helper method...

private static string GenerateFunction(string functionName, string[] parameters, string statements)

{

    StringBuilder builder = new StringBuilder();

 

    builder.AppendFormat("def {0}(", functionName);

 

    for (int i = 0; i < parameters.Length; i++)

    {

        if (i > 0) builder.AppendFormat(", ");

        builder.AppendFormat(parameters[i]);

    }

 

    builder.AppendFormat("):\r\n    ");

 

    builder.AppendFormat(statements.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", "\r\n    "));

 

    return builder.ToString();

}


You can no doubt see where this is going – so given a statement like this:

    GenerateFunction("func", new string[] { "user", "age" }, "return user + ' is ' + str(age) + ' years old'")


We’d end up with a string like this:

    def func(user, age):
       
return user + ' is ' + str(age) + ' years old'

And, moving on from there we build a test…

[Test]

public void GeneratingPythonFunction()

{

    Dictionary<string, object> context = new Dictionary<string, object>();

    context.Add("user", "alex");

    context.Add("age", 26);

 

    List<string> parameters = new List<string>(context.Keys);

    List<object> values = new List<object>(context.Values);

 

    PythonEngine engine = new PythonEngine();

 

    engine.Execute(

        GenerateFunction("func", parameters.ToArray(), "return user + ' is ' + str(age) + ' years old'"));

 

    PythonFunction func1 = engine.EvaluateAs<PythonFunction>("func");

 

    engine.Execute(

        GenerateFunction("func", parameters.ToArray(),

                        "return user + ' is ' + str(age+1) + ' years old next year'"));

 

    PythonFunction func2 = engine.EvaluateAs<PythonFunction>("func");

 

    object result1 = func1.Call(values.ToArray());

    Assert.AreEqual("alex is 26 years old", result1);

 

    object result2 = func2.Call(values.ToArray());

    Assert.AreEqual("alex is 27 years old next year", result2);

}


Are there problems with this?… well there’s a few, unsurprisingly!

•    PythonFunction’s are not delegates.
•    String manipulation is a bit clunky
•    Because these are named functions, you probably want to lock against some object when generating the function, to avoid cross-threading issues…

Back to delegates

Aside from the problems it works alright, but what we really want is a delegate… those PythonFunctions don’t give you the flexibility to substitute IronRuby in the future now do they?

So first off, lets declare a delegate suitable for our purposes.

    [ThereBeDragons("Only use as a last resort")]

    public delegate object UntypedDelegate(params object[] parameters);


Aint she a beauty ;o) - full credit for the ThereBeDragons attribute goes to Ayende, though it's probably not really warranted in this situation - now, let’s rework the last test to use a delegate instead, a simple anonymous delegate will do the dirty work:

[Test]

public void UntypedDelegateForPythonFunction()

{

    Dictionary<string, object> context = new Dictionary<string, object>();

    context.Add("user", "alex");

    context.Add("age", 26);

 

    List<string> parameters = new List<string>(context.Keys);

    List<object> values = new List<object>(context.Values);

 

    PythonEngine engine = new PythonEngine();

 

    engine.Execute(GenerateFunction("func",parameters.ToArray(),"return user + ' is ' + str(age) + ' years old'"));

 

    PythonFunction func1 = engine.EvaluateAs<PythonFunction>("func");

 

    UntypedDelegate func1Delegate = delegate(object[] param)

    {

        return func1.Call(param);

    };

 

    object result1 = func1Delegate(values.ToArray());

    Assert.AreEqual("alex is 26 years old", result1);

}


It’s certainly better then passing around PythonFunction instances – though you need to be a little careful… there’s a little quirk here, if we were to use:

    object result1 = func1Delegate.DynamicInvoke(values.ToArray());


It’s going to fail because the wrong number of arguments were supplied (it expects only 1, an array), so our delegate doesn’t really behave like it has multiple parameters.. so to dynamically invoke this delegate we’d need to take special care, promoting the arguments into a second array like so:

    object result1 = func1Delegate.DynamicInvoke(new object[] { values.ToArray()});

Next Time

When I get bored I'll write a version which doesn't require the clunky generator and post it up...

Next time I'll talk about writing classes which are python-friendly, riveting stuff eh? As you were.
posted @ Saturday, September 30, 2006 4:38:11 AM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | Trackback |

I felt like posting some code snippets… so I was looking around and thought perhaps some IronPython might be interesting…. Some of you may find this useful, possibly not.  I tend to explore functionality using unit tests, so all my code will be test cases – I might make this part of a small series on looking at python code with a view to integrating it into an existing system as a scripting engine.  Lets call the series "IronPython is your friend"... because it really is.

Part 1 - IronPython and delegates….

So you have downloaded the IronPython runtime dll's, and you would like to start integrating it into some project your working on… first off lets create a python engine, and make sure it goes….

[Test]

public void GetGoing()

{

    PythonEngine engine = new PythonEngine();

    engine.Execute("myString = 'hello'");

    Assert.AreEqual("hello reader", engine.Evaluate("myString + ' reader'"));

}


That was pretty easy; we executed some python code to assign the value hello to myString, and then evaluated the expression – sweet you might say – no effort required.

Now, you could write your scripting engine so that it passed the strings to the engine for evaluation every time you wanted to evaluate them – but this strings malarkey isn’t very pleasant… what we really want is a decent god fearing .Net delegate, so we can call this without having to think about the python engine…. Lets check out our first destination, method delegates.


Method Delegates

Given a converter delegate like this:

    Converter<int, string>

(Which by the way, if you haven’t stumbled across the generic Converter<TInput,TOutput> delegate yet, effectively you end up with a delegate method of the definition“TOutput Converter(TInput)” – pretty handy actually)
 
We could do this:

[Test]

public void StatementsToStronglyTypedDelegate()

{

    PythonEngine engine = new PythonEngine();

    List<string> parameters = new List<string>(new string[] {"age"});

    Converter<int, string> converter =

        engine.CreateMethod<Converter<int, string>>("return str(age+1)", parameters);

    Assert.AreEqual("11", converter(10));

}


Using a strongly typed overload for the engine's CreateMethod method, we’re able to evaluate statements using our new delegate, which in turn uses our IronPython method we created earlier… looks promising!

Events

Now delegates are cool,  but it’s only half the story for scripting in an application – depending on your model, you may be aiming to produce results similar to a microsoft’s VBA in applications like Excel or Word – and for this we need to be able to assign python code to events on our own classes… we can do this using delegates of course, or we could let the python (user code) do this itself, and save some time…. So lets have a look at the latter:

[Test]

public void HookingToEvents()

{

    PythonEngine engine = new PythonEngine();

 

    ManualResetEvent waitHandle = new ManualResetEvent(false);

    WebClient client = new WebClient();

 

    List<string> results = new List<string>();

 

    engine.Globals["client"] = client;

    engine.Globals["results"] = results;

 

    engine.Execute(

        @"def storeResult(sender, e):

    results.Add(e.Result)

    e.UserState.Set()

 

# assign out storeResult function as an event handler for the client class

client.DownloadStringCompleted += storeResult

");

 

    client.DownloadStringAsync(

        new Uri(

            "http://api.feedburner.com/awareness/1.0/GetFeedData?uri=http://feeds.feedburner.com/BitterCoder"),

        waitHandle);

 

    Assert.IsTrue(waitHandle.WaitOne(10000, false), "timeout occured after 10 seconds");

 

    Assert.AreEqual(1, results.Count);

 

    Assert.IsTrue(results[0].StartsWith("<?xml"));

}

     
This test is a bit clunky, but you get the idea… we are:
  • Creating a web client, for downloading some xml from a web site.
  • Set the client as a global variable for the default module in the python engine.
  • Execute some python, which creates a function, and assigns that function as a handle for the web client's DownloadStringCompleted event (take note at how concise the code is, this would be a lot uglier in C#)
  • start the async download
  • Make sure we got a response, and that the python code did what it should.
All in all, with a little finesse you can recreate a model similar to VBA, driving your extension points from events – and this certainly has some merit – obviously you want to create the code in their own modules, as opposed to using the default module for the python engine as we are.

We haven't quite finished with delegates yet though, I'll leave that for part 2 - which I'll post shortly.


posted @ Saturday, September 30, 2006 3:29:21 AM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | Trackback |
 Wednesday, September 27, 2006

It seems like batching is a data access "theme" at the moment (much like Castle, IOC & IronPython are all themes in the NZ Blogspace at the moment... even though none of them could be considered new, I've been using Castle IoC container for 1 & 1/2 years now!) - no sooner do I discover the batching support in the Rhino.Commons library, it appears NHibernate now supports it in the latest beta drop and it's a planned feature for the upcoming version 3 of the enterprise library ... It's interesting that this support never made it's way into the framework itself for version 2.0, it's all but there.

On the flip side, for NHibernate at least, the performance boons of batching are impressive (see below for an image from Ayende's blog where he's done some profiling), I haven't checked Base4.net out in a while, but I wonder if Alex James has had a chance to implement batching support yet? - maybe I should make that my task for the weekend (checking out base4.net again that is heh... not implementing support ;o)