Riff player update, and random things I learned about wxpython

Over the holidays I decided to revisit my riffplayer project, and see if I could rework it a bit, using wxpython for the GUI, and gstreamer to handle media playback. The advantage to this approach is that I get native widgets and dialogs, and I'm no longer dependent on openGL acceleration for video playback.

Obligatory screenshot of current progress riff player screenshot

After several hours, and 500 lines of python or so, I can say that I'm reasonably happy with both wxpython and gstreamer so far. With both though, there were a few things that I either had to figure out by trial and error, or piece together from random blogs. For the benefit of anybody who might run across this page, here are a few things I learned.

1.) Don't bother with IDs. The wxpython wiki's tutorial and examples usually show widgets being created with explicit integer ids, and then being later referenced by these same ids. Working this way requires you to worry about making sure each id is unique, and then having to look up the id later when you get ready to bind events to widgets. Instead, just use -1 as the id, and use the instance of the widget directly instead of using the id.

2.) Use built-in event hooks for updating the gui and background tasks. Rather than using a separate thread to update state in your application (an approach shown in one of the tutorials), use the built-in event hooks: wx.EVT_UPDATE_UI and wx.EVT_IDLE to update your gui or perform background tasks. Just bind these as you would any other events

An example, (from my media player):

class RiffPlayerFrame(wx.Frame):

  def __init__(self, parent, id, title):
    """Initialize the main riff player frame."""
    wx.Frame.__init__(self, parent, wx.ID_ANY, title, size = (700, 500))
    self.Bind(wx.EVT_CLOSE, self.Destroy)

   .... (omitted)

    self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI)
    self.Bind(wx.EVT_IDLE, self.OnIdle)

  def OnIdle(self, event):
    """Event handler for the Idle task.
    
    Used to update non-gui background processes
    """
    if not int(time.time()) % 5:
      if not self._sync_done:
        self._ApplyOffset()
        self._sync_done = True
    else:
      self._sync_done = False

  def OnUpdateUI(self, event):
    """Event handler for the EVT_UPDATE_UI psuedo-signal."""
    self.offset_timer.SetLabel('Offset: %s' % self.offset)
    if self.synced:
      self.sync_button.SetValue(True)
      self.riff_slider.Disable()
    else:
      self.sync_button.SetValue(False)
      self.riff_slider.Enable()
    try:
      vid_duration_nano, _ = self.video.query_duration(gst.FORMAT_TIME)
      vid_position_nano, _ = self.video.query_position(gst.FORMAT_TIME)
      riff_duration_nano, _ = self.riff.query_position(gst.FORMAT_TIME)
      riff_position_nano, _ = self.riff.query_position(gst.FORMAT_TIME)
    except Exception, e:
      # will be raised if stream isn't rolled for playback
      return
    self.video_timer.SetLabel(self._FormatTimestamp(vid_position_nano))
    self.video_slider.SetValue(vid_position_nano/1000000000)
    self.riff_timer.SetLabel(self._FormatTimestamp(riff_position_nano))
    self.riff_slider.SetValue(riff_position_nano/1000000000)

  1. When using gstreamer to play multiple files simultaneously, you can cram multiple playbins into a single pipeline. After fighting with adders and demuxers for several hours, I came across an example that used this approach.

An example:

  def _InitGstreamer(self):
    # set up gstreamer pipeline
    self.player = gst.Pipeline('player')

    self.riff = gst.element_factory_make('playbin', 'riff-pbin')
    self.riff.set_property('volume', 5.0)
    self.video = gst.element_factory_make('playbin', 'video-pbin')
    self.video.set_property('volume', 5.0)

    self.video_sink = gst.element_factory_make('autovideosink', 'video-sink')
    self.video.set_property('video-sink', self.video_sink)

    self.player.add(self.riff, self.video)
    bus = self.player.get_bus()
    bus.add_signal_watch()
    bus.enable_sync_message_emission()
    bus.connect('message', self.OnMessage)
    bus.connect('sync-message::element', self.OnSyncMessage)

Permanent Link

Thought experiment for greens

Here's a fun thought experiment for the green crowd. Imagine, for a moment, that scientists determine that cancer is, in fact, just a complex allergic reaction to dolphins, and that the only way to be completely rid of it is to completely exterminate dolphins from the earth.

Would you do it?

It may seem like a silly question, until you realize that up to 20 million third world children may have died of malaria since DDT was banned under the spurious theory that it caused thinning in the eggshells of wild birds.

20 million. Bird shells.

The truth is, most environmentalism is rooted in a deep-seated hatred of humankind and human progress. If solar ever becomes a viable energy source capable of sustaining a vibrant manufacturing economy, it will be immediately demonized. Bet on it.

Permanent Link

Riff Player

People who know me probably know that I'm a fan of the 90's tv show Mystery Science Theater 3000, which consisted of a thinly-plotted excuse to make fun of bad movies.

The show has been off the air for several year, but several of the principles have kept the spirit of the show alive with various projects. One of these projects, called RiffTrax, involves the same crew creating commentary tracks making fun of modern movies. The idea here is that you sync up the commentary track with the dvd, and hilarity will (presumably) ensue. To help with the syncing part, they produce a free 'Riff Player' application that will attempt to keep the commentary in sync with the video. The problem is, it's Windows only, and it only works for actual DVDs, not bare video files. Since they didn't seem motivated to fix this, I decided to roll my own.

My version is written in python, using the pyglet library to render the video and draw the interface. Because pyglet use OpenGL to do all it's rendering, it runs on Windows, Mac, and Linux. It's already usable enough to satisfy most of what I want, including the ability to save and load sync points from a local sqlite database (using SHA1 hashes of the file data to hopefully prevent overlaps), and has separate volume controls for the riff and video tracks. To make it easier to collaboratively maintain a database of common sync points, I'm creating an appengine app that will eventually be checked for sync-data automatically.

Obligatory screenshot:

Permanent Link

Free speech

I tend to be an absolutist about most things, and freedom of speech is no exception. Some of my favorite quotes on the subject.

It is error alone which needs the support of government. Truth can stand by itself --Thomas Jefferson

And John Milton, who pretty much says it as well as it could be said.

And though all the winds of doctrine were let loose to play upon the earth, so Truth be in the field, we do injuriously by licensing and prohibiting to misdoubt her strength. Let her and Falsehood grapple; who ever knew Truth put to the worse in a free and open encounter?. .... She needs no policies, nor stratagems, nor licensings to make her victorious; those are the shifts and the defences that error uses against her power: give her but room, and do not bind her when she sleeps

Permanent Link

The Freedom People

Here's a question that I can't seem to get a straight answer on from the Neocons: do you still believe that America is, and should be, governed by the constitution?

Increasingly I believe that the GOP views the constitution the way some liberal Democrats do: as an inconvenient anachronism. I can assure you that it wasn't always that way. I'm old enough to remember a time when the GOP talked openly about constitutional government and a return to constitutional principles. It isn't the libertarians who're deserting the Republican party, but you who are deserting us.

For us, the text of the constitution didn't change on 9/11. We recognize the dangers posed by radical islam, but also the dangers posed by a surveillance society. We believe in national defense, but not nation building. We believe that the government's responsibility is to ensure our liberty rather than prop up puppet governments in the Middle East. We believe that guarding against terror at the expense of liberty is a bad deal. We believe in all the amendments, even the ones that recognize the freedom to do things we don't like. We're the freedom people.

Yes, Dr. Paul isn't an imposing figure. He's not a powerful speaker. He isn't particularly good looking or notable in any way. He's probably not 'electable'. But he is sincere. He honestly believes in freedom and constitutional government. Given that, he could have buck teeth, a lisp, and a wooden leg and he'd still get my vote. I'm not voting for the man, but for his principles. Can you say that you believe that the candidate you support wants to be president for the right reasons? I can.

Permanent Link

Elections, War in Iraq, Divorcing the GOP, and Ron Paul

Most of my posts in the last few months have been decidedly non-political. This is partially by design (hopefully more on that soon), but partially because I didn't have a complete articulation of my political thoughts of late. I always get more politically aware as elections get closer, especially presidential election years, and 2008 is no exception.

This cycle is interesting though, for several reasons.

For one thing, the political situation is complex. Since Cheney is too old and too frail to run, the nomination is up for grabs on both sides. Further, the GOP is coming off a poor showing in the 2006 mid-terms and seems to lack political momentum. Under normal circumstances, this would be a slam dunk for the Democrats, except they seem hell bent on nominating candidates who'll have a tough time in the general election. Obama is a handsome, well-spoken guy, but he hasn't been forced to take a real stand on anything yet, and will lose some of his appeal in the likely-to-be dirty primary fight with Hillary. Hillary has a ton of baggage and comes across as a shrew when speaking publicly. There also looks like there might be some fire behind the Hsu donation smoke, which could cause her some scandal at a bad time. Nobody else on the Democratic side has anywhere near the record, presence, or name recognition to run and win.

On the Republican side, Giuliani has name recognition and the potential to put New York up for grabs, but is way more liberal than most of the base realizes, has a pretty odd marital history, and is unattractively authoritarian. Romney has the money and good momentum in the primary states. He looks presidential (which is more important than I'd like to admit), and is polling well. His Mormanism could prove to be an issue, and he isn't very likable as a public speaker. Fred Thomson has the presence, the face recognition and has a pretty conservative record. All things considered, he may have the best chance to win the general election if he can get through the primaries without any scandals. Huckabee, Hunter, and Brownback lack any real defining characteristics that make them stand out from the crowd. That leaves Ron Paul, who I'll come back to in a second.

Up till now, I've been talking about the election the way that most political pundits do: carefully gauging candidates based on how "electable" they are, and what chance they have against various opponents. In most of the elections I've participated in, that's exactly how I've thought. As I said earlier though, this cycle is different.

Most people who know me, whether in person or just through this blog, would probably identify me as conservative. For the most part, those people would be right. I've voted overwhelmingly Republican in every election, and on at least one occasion I've even voted a "straight ticket". People who know me best though, know that I'm probably closest to Libertarian in my leanings. In fact, my voter registration has always been "Unafilliated", and I've voted for Libertarian candidates several times on the local level. In the past, I've agreed with the GOP enough (and disagreed with the Democrats engough) to vote Republican. Even when I didn't love the Republican nominee (Dole, Bush in 04), I felt comfortable that they were the lesser of two evils, and cast my vote with that in mind.

More and more however, I find myself at odds with the Republican position. For example, I disagree strongly with the war in Iraq. It's foolish to think that Democracy is exportable, and it's clear that the founders desired that we not involve ourselves in foreign conflicts. As Jefferson wrote "I am for free commerce with all nations, political connection with none, and little or no diplomatic establishment". Historically opposed to "nation building", the Republican party has suddenly become enamored of spending blood and treasure to prop up a puppet government in the middle east. We've spent hundreds of billions of dollars to accomplish nothing. If Iraq spontaneously emerged as a perfect Jeffersonian democracy tomorrow, it would mean absolutely nothing for American citizens, in whose name this war is being waged. Further, as part of the "war on terror", we're being asked to give up more and more freedom in exchange for the illusion of safety. A Republican president, with the full support of a Republican congress, is presiding over the largest growth in government scope since the New Deal. We're spending out of control, ignoring the potential future menace of China, eroding personal liberty, and needlessly embroiling ourselves in Middle Eastern politics; and we're doing it all under the auspices of Republican administration.

There simply isn't any way to justify it anymore. The Republican party is clearly no longer either on the side of liberty or focused on the Constitution. I can't in good conscience support any Republican candidate who's following this party line.

Which leads me to Ron Paul. If you haven't heard of him, I wouldn't be surprised. Even though he's running for the Republican nomination, he's hated and mocked by most of the GOP establishment. In what seems to be a deliberate attempt to minimize his candidacy, he's receiving no support from the Republican party and no media coverage even from more "conservative" outlets. While I can't do his record or his positions full justice, suffice it to say that he is my perfect candidate. He is a lover of the Constituion, a friend of liberty, intelligent, well-spoken, and with a congressional record unmatched in it's reflection of Constitutional principles. I simply can't recommend him enough. I don't believe that I'll ever see another nominee who so closely matches my ideology. I've already donated to his campaign twice, and I'll likely donate more if, by some miracle, he were to win the nomination. Please take the time to visit his website, read his positions, and watch him speak. If you can, donate to his campaign.

If Ron Paul doesn't win the nomination, it's likely that I won't vote Republican in the coming election. Even if it means that Hillary becomes president, we need to take back the Republican party or leave it completely.

Permanent Link

TinyUrl clone

Yesterday, while listening to lugradio, I heard Aq mention someone creating a tinyurl clone that allowed you to specify your own link "handle" to reference the url (tinyurl uses a random alphanumeric id). That got me thinking about the usefulness of having my own personal url aliasing service. I use del.icio.us to manage my bookmarks, but I can definitely see the use-case for named, shortcut bookmarks for things of particular interest.

My second thought was how trivial this would be to implement in django. Result: 44 lines of code and 75 lines of html later, and I have my own tinyurl service. You can see the working implementation here (disclaimer: don't assume it's remotely bug-free, secure, or well implemented.)

the model

from django.db import models

class Link(models.Model):
    handle = models.CharField(maxlength=255,unique=True)
    url = models.CharField(maxlength=255)

    def __str__(self):
        return self.handle

    class Admin:
        pass

The views

from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponseNotFound
from django.shortcuts import render_to_response, get_object_or_404
from django.newforms import form_for_model
from django.utils import simplejson
from jonallie.tinylink.models import Link

def index(request):   
 LinkForm = form_for_model(Link)
    message = ""
    if request.method == 'POST':
        form = LinkForm(request.POST)
        if form.is_valid():
            form.save()
            message = "Form saved: access your new link at http://jonallie.com/tinylink/go/%s" % form.data['handle']
            form = LinkForm()
        else:
            message = "Form NOT saved. Check errors below"
    else:
        form = LinkForm()
    return render_to_response('tinylink/index.html',{'form':form,'message':message})
            
def redirect(request,link_handle):   
    link = get_object_or_404(Link, handle=link_handle)
    return HttpResponseRedirect(link.url)
    
def check_handle(request,link_handle):  
    link = get_object_or_404(Link,handle=link_handle)
    return HttpResponse(simplejson.dumps({'handle':link.handle}), mimetype='application/javascript')

Permanent Link

Password expiration notification in python

I've seen several scripts to notify users of impending AD password expiration, but nearly all of them are vbs. Here's a script to accomplish the same goal using python-ldap.

#!/usr/bin/env python
"""
    password_notify.py:
        provides notification of active directory password expiration via email

"""

import sys
import time
import smtplib
import ldap
import syslog

ldaphost = 'HOST'
ldapbase = 'SEARCHBASE'
ldapfilter = '(&(objectCategory=person)(objectClass=user)(!(pwdLastSet=0)))'
ldapuser = 'USER'
ldappass = 'PASS'
mailhost = 'MAILHOST'
from_address = 'donotreply@YOURDOMAIN.COM'
expiry_days = 90
notify_days = 5 # days before expiration to start notification



msg_template = """\
To: %s
From: %s
Subject: Password Expiration Notification

This message is to notify you that your directory password will expire
%s . Please change it before that time using one of the following methods: 

1.) Reboot or logoff/logon your PC and follow the password change prompts.

2.) If you're a Webmail user, you can change your password from within webmail by choosing 
'Options' from the menu on the left, then clicking the 'Change Password' button 
near the bottom of the page. In the popup window that appears, type 'DOMAIN' (no quotes) 
into the field labled 'Domain', and populate the rest of the fields with your username,
old password, and new password twice.

3.) If you're currently logged into your PC _as yourself_, you can change your password
by pressing Ctrl-Alt-Del, and clicking the 'Change Password...' button

[ password guidelines here ]

You'll continue to receive this message each day until your password expires,
or until you reset it using one of the procedures above.

Thanks,

The Helpdesk
"""
def exit_with_error(errorstr, errno=2):
    """Print error to screen and exit"""
    print errorstr
    sys.exit(2)

def wintime_to_epoch(wintime):
    """Convert windows time stamps to epoch seconds"""
    return (int(wintime)-116444736000000000)/10000000 

def epoch_to_wintime(epoch_secs):
    """Convert unix epoc seconds to windows time units"""
    return (int(epoch_secs)*10000000)+116444736000000000
    
cur_win_time = epoch_to_wintime(time.time())

# explicitly open a syslog connection
syslog.openlog("password_notify.py")

# open a connection to the mail server
smail = smtplib.SMTP(mailhost)

# attempt to establish a connection to the ldap server
try:
    conn = ldap.open(ldaphost)
    conn.simple_bind_s(ldapuser, ldappass)
except:
    syslog.syslog("Error opening connection to %s" % ldaphost)
    exit_with_error("Ldap connection error") 

# grab the list of "person" objects from AD
try:
    users = conn.search_st(ldapbase, ldap.SCOPE_SUBTREE, ldapfilter)
except: 
    syslog.syslog("Error retreiving userlist using filter: %s" % ldapfilter)
    exit_with_error("LDAP search error")
    

for user in users:
    try:
        name = user[1]['cn'][0]
        mail = user[1]['mail'][0]
        last_set = long(user[1]['pwdLastSet'][0])
        expires_win = last_set + expiry_days*24*60*60*10000000
        notify_win = expires_win - notify_days*24*60*60*10000000
    except:
        continue 
    if cur_win_time > notify_win and cur_win_time < expires_win:
        syslog.syslog("Sending notification to: %s" % mail)
        smail.sendmail(from_address,mail,msg_template % (mail, from_address,time.ctime(wintime_to_epoch(expires_win))))

sys.exit(0)

Permanent Link

Better net-bridge script

I recently set up an openvpn server on Ubuntu LTS using bridged networking and was really dissatisfied with the sample scripts for bringing the bridge adapter up/down. The script below is what I came up with instead:

    # startup script to establish bridged network
    # based on sambple scripts from openvpn
    
    function exit_with_error {
        echo $*
        exit 2
    }
    
    # bridged interface
    br="br0"
    
    # tap interface
    tap="tap0"
    
    # physical address
    eth="eth0"
    eth_addr="172.16.1.28"
    eth_mask="255.255.0.0"
    eth_cast="172.16.255.255"
    eth_gw="172.16.32.2"
    
    case "$1" in
        "start")
            echo "Starting bridged networking"
            
            # create tunnel
            /usr/sbin/openvpn --mktun --dev $tap
            [ "$?" != "0" ] && exit_with_error "Failed to create tunnel"
            
            /usr/sbin/brctl addbr $br
            /usr/sbin/brctl addif $br $eth
            /usr/sbin/brctl addif $br $tap
            [ "$?" != "0" ] && exit_with_error "Failed to create bridge"
            
            /sbin/ifconfig $tap 0.0.0.0 promisc up || exit_with_error "Failed to configure $tap"
            /sbin/ifconfig $eth 0.0.0.0 promisc up || exit_with_error "Falied to configure $eth"
            /sbin/ifconfig $br $eth_addr netmask $eth_mask broadcast $eth_cast || exit_with_error "Failed to configure $br"
            #/sbin/ifconfig $br up
            
            # add default route
            /sbin/route add -net default gw $eth_gw
    
        # create iptables rules
        /sbin/iptables -A INPUT -i $br -j ACCEPT
        /sbin/iptables -A INPUT -i $tap -j ACCEPT
        /sbin/iptables -A FORWARD -i $br -j ACCEPT
        /sbin/iptables -A FORWARD -i $tap -j ACCEPT
            ;;
            
        "stop")
            echo "Stopping bridged networking"
            
            # bring down bridge
            /sbin/ifconfig $br down
            
            # tear down bridge
            /usr/sbin/brctl delbr $br
            /usr/sbin/openvpn --rmtun --dev $tap
            
            # reset address of eth0
            /sbin/ifconfig $eth $eth_addr netmask $eth_mask broadcast $eth_cast
            #/sbin/ifconfig $eth up
            /sbin/route add -net default gw $eth_gw
    
        # flush iptables rules
        /sbin/iptables -F INPUT
        /sbin/iptables -F FORWARD
            ;;
            
        *)
            echo "Usage: $0 {start|stop}" >&2
            exit 1
            ;;
    esac
    
    exit 0

Permanent Link

Stupid Webmin Tricks

I've been working alot with webmin in the past view weeks, and learning a ton along the way. I'm sure that most of this is already well documented (like the rest of webmin) but I didn't know it, and that makes it important. :-)

first trick: custom, "unattended" installs. Webmin's main setup script calls two external (and by default non-existent) scripts during the install process. By creating these two scripts, and placing them in the webmin install directory, you can "pre-set" various options; any variable pre-set in this way won't be prompted for during install. This is great for maintaining a standard configuration across multiple systems.

The first script, setup-pre.sh is called at the beginning of the setup script, and is primarily useful for pre-setting variables.

My commented example:

    ####################################### 5-2005 ##
    #
    # setup-pre.sh:
    #
    #  called by setup.sh. sets up pre-defined
    #  variables that will be used as defaults
    #  by webmin setup rather than prompting
    #  the user. This allows for partially or
    #  completely automated installs. Variables
    #  declared here _won't_ be prompted for at
    #  install.
    # 
    # author: jon allie  [ jon (at)jonallie(dot)com 
    ##################################################
    
    
    ## $config_dir: directory for webmin to store it's config files
    #   will be created by the installer if it doesn't exist.
    config_dir=/opt/freeware/webmin/config
    
    ## $var_dir: directory for webmin to store it's log files
    #    will be created by the installer if it doesn't exist.
    var_dir=/opt/freeware/webmin/log
    
    ## $perl:  full path to the perl executable.
    perl=/usr/bin/perl
    
    ## $os_type: os type, see os_list.txt for possible values
    os_type=aix
    
    ## $os_version: version number of os_type specified above
    os_version=5.2
    
    ## $real_os_type
    real_os_type=IBM AIX
    
    ## $real_os_version
    real_os_version=5.2

    ## $port: port for webmin server to listen on.
    port=10000

    ## $login: webmin username
    login=admin

    ## $password: plain text password. don't use this
    #password=

    ## $crypt: pre-encrypted password. set this to 'x' to enable unix authentication
    crypt=x

    ## $ssl: whether or not to use ssl. 1 for yes, 0 for no
    # the Net::SSLeay library MUST be installed for 
    # this to function. 
    ssl=1
    
    ## $atboot: whether or not to start webmin at boot.
    atboot=1

setup-post.sh is, of course, called at the end of the setup process, and can be used for just about anything. Here, I'm disabling the (stupidly default) options to remember passwords and to display the system hostname on the login page.

    ####################################### 5-2005 ##
    #
    # setup-post.sh:
    #
    #  called by setup.sh. this script is called
    # as a final step before starting webmin
    # at the end of install. Used to set final
    # options.
    #
    # author: jon allie [ jon(at)jonallie(dot)com ]
    ##################################################
    
    ### set shorter name for location of global config file
    gconfig=$config_dir/config

    ## exit if global config isn't writable
    if [ ! -w $gconfig ]; then
        exit 0
    fi

    ### set extra security options in global config

    ## disable the option to remember passwords
    echo "noremember=1" >> $gconfig

    ## disable display of hostname on login page
    echo "nohostname=1" >> $gconfig

Permanent Link