computer stuff

SOLUTION: WebSockets, Django Channels, MacOS 10.12, Server 5.2, Apache 2.4.23

Fri 13, January 2017

Finally…

I have WebSocket connections working via Apache in MacOS Server 5.2.

As this caused a lot of head/heartache over the last few weeks I thought I’d better share what I experienced in case someone finds themselves in the same boat!

And the solution is simple.

The short version:

I use MacOS Server 5.2 (ships with Apache 2.4.23) to run a python Django application via the mod_wsgi module.

I had been trying to setup proxypass and wstunnel in MacOS 10.12 & Server 5.2 to handle websocket connections via an ASGI interface server called Daphne running on localhost on port 8001.

I wanted to reverse proxy any WebSocket connection to wss://myapp.local/chat/stream/ to ws://localhost:8001/chat/stream/

From what I had read on all the forums and mailing lists that I had scoured was to simply make some proxypass definitions in the appropriate virtual host and make sure that the mod_proxy and mod_proxy_wstunnel modules were loaded and it would work.

Long story short – from what I understand all of this trouble came down to MacOS Server 5 and one major change:
“A single instance of httpd runs as a reverse proxy, called the Service Proxy, and several additional instances of httpd run behind that proxy to support specific HTTP-based services, including an instance for the Websites service.”

All I needed to do to proxy the websocket connection was the following:

in:
/Library/Server/Web/Config/Proxy/apache_serviceproxy.conf

Add (around line 297 (in the section about user websites, webdav):
ProxyPass / http://localhost:8001/
ProxyPassReverse / http://localhost:8001/

RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
RewriteRule .* ws://localhost:8001%{REQUEST_URI} [P]

I then kicked over the service proxy:

sudo launchctl unload -w /Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons/com.apple.serviceproxy.plist
sudo launchctl load -w /Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons/com.apple.serviceproxy.plist

And the web socket connections were instantly working!

The long version:

For many weeks I have been trying to get WebSocket connections functioning with Apache and Andrew Godwin’s Django Channels project in an app I am developing.

Django Channels is “a project to make Django able to handle more than just plain HTTP requests, including WebSockets and HTTP2, as well as the ability to run code after a response has been sent for things like thumbnailing or background calculation.”

My interest in Django Channels came from my requirement of a chat system in my webapp. After watching a several of Andrew’s demos on youtube and having read through the docs and eventually installing Andrew’s demo django channels project I figured I would be able to get this working in production on our MacOS Server.

The current release of MacOS 10.12 and Server 5.2 ships with Apache 2.4.23. This comes with the necessary mod_proxy_wstunnel module to be able to proxy WebSocket connections (ws:// and secure wss://) in Apache and is already loaded in the server config file:

/Library/Server/Web/Config/apache2/httpd_server_app.conf

Daphne is Andrew’s ASGI interface server that supports WebSockets & long-poll HTTP requests. WSGI does not.

With Daphne running on localhost on a port that MacOS isn’t occupying (i went with 8001) the idea was to get Apache to reverse proxy certain requests to Daphne.

Daphne can be run on a specified port (8001 in tis example) like so (-v2 for more feedback):
daphne -p 8001 yourapp -v2

I wanted Daphne to handle only the web socket connections (as I use currently depend on some apache modules for serving media such as mod_xsendfile). In my case the websocket connection was via /chat/stream/ based on Andrew’s demo project.

From what I had read in MacOS Server’s implementation of Apache the idea is to declare these proxypass commands inside the virtual host files of your “sites” in:

/Library/Server/Web/Config/apache2/sites/

In config files such as:
0000_127.0.0.1_34543_.conf

I did also read that any customisation for web apps running on MacOS Server should be made to the plist file for the required web app in:
/Library/Server/Web/Config/apache2/webapps/
In a plist file such as:
com.apple.webapp.wsgi.plist
Anyway…

I edited the 0000_127.0.0.1_34543_.conf file adding:

ProxyPass /chat/stream/ ws://localhost:8001/
ProxyPassReverse /chat/stream/ ws://localhost:8001/

Eager to test out my first web socket chat connection I refreshed the page only to see an error printed in the apache log:

No protocol handler was valid for the URL /chat/stream/. If you are using a DSO version of mod_proxy, make sure the proxy submodules are included in the configuration using LoadModule.

I had read of many people finding a solution at least with Apache on Ubuntu or a custom install on MacOS.
I even tried installing Apache using Brew and when that didn’t work I almost proceeded to install nginx.

After countless hours/days of googling I reached to the Apache mailing list for some help with this error. Yann Ylavic was very generous with his time and offered me various ideas on how to get it going. After trying the following:

SetEnvIf Request_URI ^/chat/stream/ is_websocket
RequestHeader set Upgrade WebSocket env=is_websocket
ProxyPass /chat/stream/ ws://myserver.local:8001/chat/stream/

I noticed that the interface server on port 8001 Daphne was starting to receive ws connections!
However in the client browser it was logging:

“Error during WebSocket handshake: ‘Upgrade’ header is missing”

From what I could see mod_dumpio was logging that the “Connection: Upgrade” and “Upgrade: WebSocket” headers were being sent as part of the web socket handshake:

mod_dumpio: dumpio_in (data-HEAP): HTTP/1.1 101 Switching Protocols\r\nServer: AutobahnPython/0.17.1\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: 17WYrMeMS8a4ImHpU0gS3/k0+Cg=\r\n\r\n
mod_dumpio.c(164): [client 127.0.0.1:63944] mod_dumpio: dumpio_out
mod_dumpio.c(58): [client 127.0.0.1:63944] mod_dumpio: dumpio_out (data-TRANSIENT): 160 bytes
mod_dumpio.c(100): [client 127.0.0.1:63944] mod_dumpio: dumpio_out (data-TRANSIENT): HTTP/1.1 101 Switching Protocols\r\nServer: AutobahnPython/0.17.1\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: 17WYrMeMS8a4ImHpU0gS3/k0+Cg=\r\n\r\n

However the client browser showed nothing in the response headers.

I was more stumped than ever.

I explored the client side jQuery framework as well as the the Django channels & autobahn module to see if perhaps something was amiss, and then revised my own app and various combinations of suggestions about Apache and it’s module. But nothing stood out to me.

Then I reread the ReadMe.txt inside the apache2 dir:

/Library/Server/Web/Config/apache2/ReadMe.txt

“Special notes about the web proxy architecture in Server application 5.0:

This version of Server application contains a revised architecture for all HTTP-based services. In previous versions there was a single instance of httpd acting as a reverse proxy for Wiki, Profile, and Calendar/Address services, and also scting as the Websites service.º With this version, there is a major change: A single instance of httpd runs as a reverse proxy, called the Service Proxy, and several additional instances of httpd run behind that proxy to support specific HTTP-based services, including an instance for the Websites service.

Since the httpd instance for the Websites service is now behind a reverse proxy, or Service Proxy, note the following:

It is only the external Service Proxy httpd instance that listens on TCP ports 80 and 443; it proxies HTTP requests and responses to Websites and other HTTP-based services.

I wondered if thus ServiceProxy had something to do with it. I had a look over:

/Library/Server/Web/Config/Proxy/apache_serviceproxy.conf

and noticed a comment – “# The user websites, and webdav”.
I figured it wouldn’t hurt to try adding the proxypass definitions & rewrite rules that people had suggested on the forums as their solution.

ProxyPass / http://localhost:8001/
ProxyPassReverse / http://localhost:8001/

RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
RewriteRule .* ws://localhost:8001%{REQUEST_URI} [P]

Sure enough after restarting the ServiceProxy it all started to work!

http://stackoverflow.com/questions/41287959/mod-proxy-wstunnel-mac-os-x-10-11-6-apache-2-4-18
https://github.com/adamteale/macos-server5-websocket/

Auto mount SMB/AFP/* volumes in Mac OS X

Wed 7, December 2016

Many moons ago I posted some findings on how to get network volumes to auto mount in Mac OS X.

Recently I needed to do this again – but things have changed a little since OS X 10.5 (currently running 10.11).

All the info I found was in this great post:

http://www.gm2dev.com/2015/01/automount-smb-shares-on-osx-yosemite/

Essentially:

edit this file:

/etc/auto_master

add something along the lines of:

/mnt auto_smb -nosuid

(this example creates a mount point on your machine “/mnt” based on the file “auto_smb” that we need to create next.

create a file such as:

/etc/auto_smb

add mount points to this auto_smb file:
(this example creates an smb mount point called “projects” which connects to an IP address of 192.168.1.102 as guest and mounts the  “allthestuff” directory from that machine)

projects -fstype=smbfs,soft ://guest@192.168.1.102/allthestuff

kick it over with:

sudo automount -vc

 

Up until now it has been working well!

 

Learning to be a Djangonaut: Localisation/translation – locale for Chile

Thu 31, December 2015
Djangonaut
A person who is expert in Django web framework.
He is a Djangonaut dude.

In my pursuit to become a gun djangonaut and get my knowledge of the framework sound and stable I have been following along Marina Mele’s fantastic series “TaskBuster Django Tutorial”. It covers a lot of topics that I am keen to understand and be able to explain one day.

My current goal is to have a django web app that has a Restful API that will allow various types of authentication. On top of this I will create an iOS app that can talk to it.

I had started to follow along with a post that Félix Descôteaux had written titled “A Rest API using Django and authentication with OAuth2 AND third parties!”. As I was getting into it I realised that I had better spend some time get a better basic understanding of Django and some other key concepts of python and web app development I had yet to explore. Geez the internet is just so cool when you want to learn something!

Along the way following Marina’s series I hit a bit trouble getting “Localizations” to work – when a site works across multiple languages. Django seems to have a easy to use mechanism but I was having issues with the locale for Chilean Spanish (I always am really…). 🙂

So in an effort to try to give back a little to the wonderful internet for anyone else who may stumble across this post with the same issue…

Basically:

It was a combination of using “es-CL”, “es_CL” & “es-cl” in various places.

 

Property Value
Base locale ID es-CL
Language es
Language Spanish
Country CL
Country Chile

In the base.py (settings file):

LANGUAGES = (
('en', _('English')),
('<strong>es-CL</strong>', _('Spanish (Chile)')),
)

Then in the test_all_users.py I used ‘es-cl‘ wherever necessary.

Then in the tb_test virtual env shell:

$ python manage.py makemessages -l es_CL

I edited the django.po with my translated strings.
(I had to laugh when I read the “po” in django.po for this Chilean translation :] )

Then in the tb_test virtual env shell:

$ python manage.py compilemessages -l es_CL

Now I am passing the tests and seeing the translated page in the browser.

I hope that helps someone out one day!

fcpxImageExporter and my 5 minutes of coding fame

Mon 2, December 2013

A little while ago at Orangutan we had a client ask us to export several hundred frames from a 1 hour video we had produced for them.

We worked in fcpx and manually exporting frames can be quite a task.

So I spent a late afternoon putting together a little app to read fcpxml and use it to export still images based on the markers we had put in the timeline.

I polished it a little more and shared it on the fcp.co forums and over the week the little app had been downloaded over 700 times.

It even got a nice review from Larry Jordan and several retweets from other fcpx pros like Richard Taylor and sites like premiumbeat.com. 

fcpxImageExporter is available over at Orangutan.

fie105

Recursive rsync with only specific file types

Mon 28, May 2012

I had never worked out how to use rsync to create intermediate directories when syncing over certain filetypes (in my case nuke’s .nk files). So the other day I googled around and found exactly what i was looking for.

A great way to sync directories and certain filetypes recursively:
example

rsync -pavr –progress –include “+ */” -include=”*.nk” -exclude=”- *” /my/source/directory /my/destination/

And all the thanks needs to go to Mike:
http://mike-is-a-geek.blogspot.com/2011/04/recursive-rsync-only-specific-file.html

fcp 7 to nuke

Wed 9, May 2012

Scenario
A FCP 7 timeline with 20 shots in it (all in one video layer).
I need to comp them all separately in nuke.
I want to have each shot in a separate numbered directory, with subdirectories for “scripts” and “renders”

Here is a little python script i wrote this morning to export a basic FCP 7 timeline as a series of nuke scripts into numbered shot directories.
This script parses an XML file that I exported from FCP 7 (the sequence/edit).

I have only used this for the purposes i needed, but it works.
Perhaps it could be expanded to incorporate multiple video layers.

Each nuke script that is generated is just a read node, with the nuke.root frame range set to the duration of the clip in FCP 7.

Even if the script doesn’t help everyone’s fcp7-nuke needs I found that i learnt a lot about Python’s elementTree XML library. Nice clean simple.
And also this page was a big help to get things going:
http://drumcoder.co.uk/blog/2010/jun/17/using-elementtree-python/

The script needs to be run from a shell/terminal:
python /path/to/the/script/fcpNuke.py /path/to/you/xmlfile.xml

*file re-uploaded 11 May 2012

The script:
fcp7toNuke (161)

NSSlider, Mouse Down & NSThread

Tue 2, August 2011

I hope the following makes some sense and is of some use to newbie Mac Developers out there.

For the past week I have been working on Sub It to try get it ready for first release on the Mac App Store.

One of the tools I have put in is a slider to adjust the movie playback rate – similar to the scrub tool in Final Cut Pro.

There were a few hurdles I had to overcome to get this to work.

Initially I realised I would have to subclass NSSlider to be able to detect/grab the MouseDown and MouseUp NSEvents (I wanted the slider to reset to 0 after the user had released the mouse – putting the movie in pause/stop) – googling revealed that NSSlider runs inside its own event loop in the main thread.

Hooking up a delegate to NSSlider subclass allowed me to call actions/methods in the main controller on those Mouse events – eventually I got this to work with the following code (and via Interface Builder I hooked up the delegate property to the my main Controller):

TimeScrubberSlider.h

#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
 
@interface TimeScrubberSlider : NSSlider {
	IBOutlet id delegate;
}
 
@property (readwrite,retain) id delegate;
 
@end

TimeScrubberSlider.m

#import "TimeScrubberSlider.h"
#import "Controller.h"
 
@implementation TimeScrubberSlider
 
@synthesize delegate;
 
- (void)mouseDown:(NSEvent *)theEvent  {
 
	[delegate doScrubTime:nil];
	[super mouseDown:theEvent];
	[self mouseUp:theEvent];
 
}
 
 
- (void) mouseUp: (NSEvent*) theEvent
{
 
	[self setIntValue:0];
	[delegate doPlay:nil];
 
}
 
@end

This was all working, but the problem was that the time display of the movie would not constantly update – only when the slider was moved would the current time update – not continuously.

So I decided to try attach the updating of the current time display to a separate thread – using NSThread. My thought was that no matter what happened in the main controllers’ thread, this other thread would always do it’s own thing and hopefully not be interrupted.

To get this going I created a method (called ‘launchTimer’) that is called when a file is loaded, and this calls the ‘updateCurrentTime’ method, and this ‘launchTimer’ method is run under the seperate thread. Umm i think that kinda makes sense.

Something along these lines:

snippets from the Controller.m

- (void)setupGUIFromFileRead{
 
	//just a snippet...
 
	//begin timer to update the current frame/time display - as separate thread so nsslider "scrub" can play nicely
	[NSThread detachNewThreadSelector:@selector(launchTimer) 
                             toTarget:self 
                           withObject:nil];
 
}
 
-(void)launchTimer{
 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSLog(@"startingtimer");
    [NSThread setThreadPriority:1.0];
    updateTimerToggle = YES;
    while (updateTimerToggle) {  // loop until cancelled
        [self doUpdateCurrentTimeDisplay:nil];
        [NSThread sleepForTimeInterval:.1];
    }
    [pool release];
 
}

mp3 to wav (a little app written in Objective-C)

Thu 28, April 2011

What?

Just knocked together a little app to batch convert selected MP3 files in iTunes to WAV format.

Seems to work pretty well so far.

When the app launches click the button to select the “Output Directory” of the converted files.

Then hit the “Go!” button.

 

Why?

I was looking for an easy way to batch convert selected tracks in my iTunes library to WAV format, and have them export into a directory (for later use in Final Cut Pro). I came across some Applescripts that would do the conversion within iTunes, but in the end wasn’t really what I was after. I started writing something in Python but then figured it would be a great opportunity to give it a go in Obj-C. It took half a day and I learnt a lot in the time it took to put it together.Hopefully people out there can find a use for the little app, or use the XCode project to build something better!


Requirements:

iTunes must be open and the tracks you want to convert must be selected

*Only tested on Mac OS X 10.6.7

 

Download:

App: Download MP3toWAV

Xcode Project: Download mp3towavXcodeProj

 

Notes:

Built using XCode and ObjC. I needed the following to talk to iTunes via OBJC:

 

 

 

some Nuke Python snippets (and other general coolness)

Tue 11, May 2010

If anyone has some cool snippets to share please feel free to leave them in a comment below.

*last updated: 23 December 2016

random particle colour with particle expression node
*** a blendMat node must be applied before the emitter:
operation: plus
surface blend: modulate

the “color” parameter of a particleExpression node can be set using a 3dVector:
v( r, g, b )

and each channel can be referenced like this:
the red value for example :
x(color)

give the colour parameter a random value – (needs to be a 3dVector)
v(random*2, random, random/1.4 )

iterate over the animation curve of a Transform node copying the value of the Y position to a parameter of another Transform node.

dest = nuke.nodes.Transform()
destTransform = dest["translate"]
destTransform.setAnimated()
destAnim = dest["translate"].animation(1)
 
a = nuke.selectedNode()["translate"]
ypos = a.animation(1)
keys = ypos.keys()
 
for a in range(0,42):
    dest["translate"].animation(1).setKey(a, ypos.evaluate(a))

 

display the current duration since the first frame (useful when sequence doesn’t start at zero/one)
put this in a text node or in a node’s “label” tab

[python {int( nuke.frame() - nuke.root()['first_frame'].value() ) }]

 

take a RotoPaint and change each layer’s RGBA to a grey level based on it’s “depth” – useful for prep’ing for a stereo conversion i’d imagine

import random
a = nuke.selectedNode()
if a.Class() == "RotoPaint":
    knob = a['curves']
    i = 0.0
    for c in knob.rootLayer:
 
        pos = float ((len(knob.rootLayer) - i) / len(knob.rootLayer))
        i = i+1
 
        attrs = c.getAttributes()
        attrs.set(1, attrs.kRedOverlayAttribute, pos)
        attrs.set(1, attrs.kRedAttribute, pos)
        attrs.set(1, attrs.kGreenOverlayAttribute, pos)
        attrs.set(1, attrs.kGreenAttribute, pos)
        attrs.set(1, attrs.kBlueOverlayAttribute, pos)
        attrs.set(1, attrs.kBlueAttribute, pos)
        attrs.set(1, attrs.kAlphaOverlayAttribute, pos)
        attrs.set(1, attrs.kAlphaAttribute, pos)

for all selected Tracker nodes multiply their track values by a factor of 4

for a in nuke.selectedNodes():
    if a.Class()=="Tracker3":
 
	def scaleMe(track):
		if track.isAnimated():
			for j in range (0,2):
		        anim = track.animation(j)
		        keys = anim.keys()
		        scaleFactor = 4
		        while keys:
		            keys.pop().y *= scaleFactor
	#i am sure there is a much nicer way of iterating over all the knobs but this worked for what i quickly needed
	t1 = a['track1']
	t2 = a['track2']
	t3 = a['track3']
	t4 = a['track4']
	scaleMe(t1)
	scaleMe(t2)
	scaleMe(t3)
	scaleMe(t4)

set bbox to “B” for nodes inside all Group nodes

import nuke
 
def bboxGroup():
  for b in nuke.allNodes():
    if b.Class() == "Group":
      for a in nuke.toNode(b.name()).nodes():
        classTypes = ['Merge' , 'Keymix', 'Copy', ]
        print a.name()
        for n in classTypes:
          if n in a.Class():
            try:
              for p in a['bbox'].values():
                if 'B' in p:
                  a['bbox'].setValue(a['bbox'].values().index(p))
            except:
              pass

“Autowrite” node
Copy the following line into the python “beforeRender” field in a write node.
The write node’s “file” field will be filled based on the script’s name/path.
Obviously this all depends on your pipeline etc.

For my current situation each vfx shot has it’s own directory, which is then populated with “renders” & “scripts” subdirectories.
So for me I can do this:

nuke.thisNode()["file"].setValue(nuke.root().name().replace("scripts","renders").replace(".nk",".mov"))

delete all nodes that are not selected

s = nuke.selectedNodes()
b = nuke.allNodes()
 
for n in b:
    if n not in s:
        nuke.delete(n)

selects all dependencies (input nodes & their parents) from a selected node

a = nuke.selectedNode()
nodesToSelect = []
 
nodesToSelect.append(a)
def climb(node):
    # print node.name()
    for n in node.dependencies():
        nodesToSelect.append(a)
        climb(n)
 
climb(a)
 
for x in nodesToSelect:
	print x.name()
    # x.setSelected(1)

set all Read nodes to cache locally

for a in nuke.allNodes():
    if a.Class()=='Read':
        a['cached'].setValue(1)
        a['cacheLocal'].setValue(0)

print last frame of script

print nuke.root()['last_frame'].value()

create a backdrop based on selected Nodes

margin = 100
xpMax = nuke.selectedNode().xpos()
xpMin = nuke.selectedNode().xpos()
ypMax = nuke.selectedNode().ypos()
ypMin = nuke.selectedNode().ypos()
 
for a in nuke.selectedNodes():
    if a.xpos() &gt; xpMax:
        xpMax = a.xpos()
    if a.xpos() &lt; xpMin: xpMin = a.xpos() if a.ypos() &gt; ypMax:
        ypMax = a.ypos()
    if a.ypos() &lt; ypMin:
        ypMin = a.ypos()
 
bd = nuke.nodes.BackdropNode(bdwidth=(xpMax-xpMin)+margin, bdheight=(ypMax-ypMin)+margin) 
bd.setXpos(xpMin-margin/2)
bd.setYpos(ypMin-margin/2)

disable “postage stamps” on only “Read” nodes

for a in nuke.allNodes():
   if a.Class()=='Read':
       a['postage_stamp'].setValue(0)

disable “postage stamps” on all nodes

for a in nuke.allNodes():
    try:
        a['postage_stamp'].setValue(0)
    except:
        pass

“unhide” all nodes’ inputs – useful when receiving a sneaky comp/lighting script

for a in nuke.allNodes():
    try:
        a['hide_input'].setValue(0)
    except:
        pass

change the “first” frame of all selected nodes that are “Read” nodes:
(example changes the first frame to 1018)

for a in nuke.selectedNodes():
    if a.Class() == 'Read':
        a['first'].setValue(1018)

print a selected nodes’ methods

import struct
node = nuke.selectedNode()
for a in node['lookup'].animations():
    print dir(a)

print inputs (dependencies) of a selected node:

for a in nuke.selectedNode().dependencies():
    print a.name()

print outputs (dependents) of a selected node:

for a in nuke.selectedNode().dependent():
    print a.name()

find all the TimeOffset nodes in a Group called “Group2”, and change the value of each offset based on it’s position in the array of found time offsets

tos = []
for a in nuke.toNode('Group2').nodes():
	if a.Class()=='TimeOffset':
		tos.append(a)
for b in tos:
	b['time_offset'].setValue(tos.index(b))

set the ‘bbox’ for any selected Merge, Keymix & Copy nodes to “B”

for a in nuke.selectedNodes():
	classTypes = ['Merge' , 'Keymix', 'Copy', ]
	for n in classTypes:
		if n in a.Class():
			for p in a['bbox'].values():
				if 'B' in p:
					a['bbox'].setValue(a['bbox'].values().index(p))

remove all animation from a selected nodes

for a in nuke.selectedNode().knobs():
	nuke.selectedNode()[a].clearAnimated()

add keyframes – animate a mix

for a in nuke.selectedNodes():
	a['mix'].setAnimated()
	a['mix'].setValueAt(1,nuke.frame())
	a['mix'].setValueAt(0,(nuke.frame() - 1))

half the colour value of all the Constant nodes in a script

for a in nuke.allNodes():
	if a.Class() == "Constant":
		a['color'].setValue(a['color'].value()[0] / 2 , 0)
		a['color'].setValue(a['color'].value()[1] / 2 , 1)
		a['color'].setValue(a['color'].value()[2] / 2 , 2)

find all the transform nodes in a script, and if their input is a Crop, set the ‘scale’ value to be twice it’s current value (also checks if the scale is a list/array or a float)

for a in nuke.allNodes():
	if a.Class() == "Transform":
		if a.input(0).Class() == "Crop":
			x = a['scale'].value()
			if type(x).__name__ == 'list':
				a['scale'].setValue(x[0] * 2 , 0)
				a['scale'].setValue(x[1] * 2 , 1)
			if type(x).__name__ == 'float':
				a['scale'].setValue(x*2)

set all the gain values of all ColorCorrect nodes to be twice their current value

for a in nuke.allNodes():
	if a.Class() == "ColorCorrect":
		a['gain'].setValue(a['gain'].value() * 2)

print files with ‘mov’ in filename

for a in nuke.allNodes():
	if 'Read' in a['name'].value():
		if 'mov' in a['file'].value():
			print a['file'].value()

change font size of all “write” nodes in script

for a in nuke.selectedNodes():
	if "Write" in a['name'].value():
		a['note_font_size'].setValue(60)

create 20 constants with incrementing colour values

def makeConstants(amount):
	for i in range(amount):
		a= nuke.nodes.Constant()
		color= float( float(i) / float(amount) )
		a['color'].setValue(color)
makeConstants(20)

change the name of all Text nodes to contents the text “message”

for a in nuke.allNodes():
    if a.Class()=='Text':
        a.setName(a['message'].value())

##Expressions
(in an expression node) – alpha channel – if the y value of the pixel is even, make it 0, else make it 1 (for viewing fields?)
y%2==1?0:1

MyShares app

Wed 14, October 2009

icon

Spent most of today learning more about NSTableView, memory management and other things Cocoa/Obj-C/xCode.

What came of it?

I made a little app for keeping track of shares – via finance.yahoo.com.

Includes an open/save dialog so you can have a ‘portfolio’ to open again at a later time.

ATM I think it only runs in OS X 10.6.

Download MyShares

myshares
Download MyShares xcode project