Creating MyTube with Flex and Turbogears, Part 1

I read an interesting tutorial at Oreilly titled Creating Mytube with Flex and Php. I've been tinkering with flex for a while now, and i enjoyed the structured approach of this tutorial and its interface with flex. The tutorial used php for the server side functionality. I do not enjoy coding with php, so i decided to re-write the tutorial(with a few modifications) but using Python and specifically using my preferred framework, the TurboGears framework. TurboGears currently uses SQLObject and Kid for its ORM and template engine by default, but TurboGears 1.1(which is a major overhaul) should be out soon and it will use, as its default options, SQLAlchemy and Genshi. So i decided to just go ahead and use them here to show that they can be used right now with the stable release and that the modifications to do in TG is minimal. First off, we have to create a quickstart project which should set up the directory structure and generate some boilerplate code. Execute the following command wherever you want to create your project:
tg-admin quickstart -s
The -s flag is used to siginify that we will use SQLAlchemy instead of the default SQLObject. During the execution of the command, you will be asked for the project name and package name and if you would like identity functionality in this project. Name the project moviestg, as well as the package, and answer no for the identity part. In this project, we will not use it. Next, let's just check that the generated application works correctly. Go inside the project directory moviestg and execute the following commands:
start-moviestg.py
After the command finishes execution, visit the address "localhost:8080" and you should see the welcome page. Next, we need to create our model. TG takes care of creating the tables we need in the database if we just go ahead and define the model as a Python class in the model.py file in moviestg/moviestg/model.py. The quickstart project has all the proper imports, so we just need to define our model.
movie_table = Table('movie_table', metadata,
        Column('movieid', Integer, primary_key=True),
        Column('title', String),
        Column('source', String),
        Column('thumb', String),
        Column('width', Integer),
        Column('height', Integer),
)

class Movie(object):
        def __repr__(self):
                return "Movie %s - %s"%(self.movieid, self.title)

assign_mapper(session.context, Movie, movie_table)
You should read the tutorial on the SQLAlchemy webpage, its very user-friendly. But for a quick hint, this code simply constructs the table along with its corresponding class and associates the two. To construct the database and corresponding table, execute the following command in the tgmovies directory.
tg-admin sql create 
Next we create the page to upload the the videos. The template directory is in tgmovies/tgmovies/templates. For now, the default generated templates are Kid templates, so just go ahead and remove all of them from the directory except the __init__.py file. We will create our own genshi template. Create a file called addmovie.html and paste the folowing code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/">

<head>

<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>

<title>Video Sharing Site</title>
</head>

<body>
	<form enctype="multipart/form-data" method="post" action="upload">
  		<table>
  			<tr><td>Title</td><td><input type="text" name="title"/></td></tr>
  			<tr><td>Movie</td><td><input type="file" name="movie"/></td></tr>
  		</table>
  		<input type="submit" value="Upload" />
  	</form>
</body>

</html>

Next, we need to edit the controller and implement the glue code between the controller, templates and model. The controller is in the tgmovies/tgmovies directory and is called controllers.py. Replace the imports at the top with the following code, we will need them later on:
from turbogears import controllers, expose, flash
from model import *
import os
import re
import string

from cherrypy.lib.cptools import serveFile
The only part that should look strange to you is the last line, the cherrypy include. This include gives us the ability to serve files through cherrypy.(HINT: this is for cherrypy 2.2.1, newer releases have changed the path) Replace the index function with the following code:
    @expose(template="genshi:moviestg.templates.addmovie")
    def index(self):
        return dict()
        
This directs the index method to show the addmovie.html genshi template. You've seen that we submit the form to the upload address. So now we have to write the upload functionality in the controller. We've already Imported the string module and re module. and so paste the following methods inside the controller

    @expose(template="genshi:moviestg.templates.upload")
    def upload(self, title, movie):
    	#the upload form submits to here and the posted
    	#fields will be recieved as arguments
    	
    	uploadedfilename = movie.filename
    	#get base name(without extension)
    	#the join part is to take care of files called example.foo.avi for example
    	basefilename = string.join(re.split("\.", uploadedfilename)[0:-1], ".")
	basepath = os.getcwd() + "/"

	insertedid = self.preminsert(title)		
	
	uploadedfilename = basefilename + str(insertedid) + ".avi"
	flvfilename = basefilename + str(insertedid) + ".flv"    	
	thumbnailname = basefilename + str(insertedid) + ".gif"
	
	
	uploadedfilepath = basepath + uploadedfilename
	flvfilepath = basepath + flvfilename
	thumbnailpath = basepath + thumbnailname

	uploadedfile = file(uploadedfilepath, "wb")
	
        while True:
            data = movie.file.read(1024*8)
            if not data:
                break
            uploadedfile.write(data)

        
        uploadedfile.flush()    
        uploadedfile.close()
	
	self.converttoflv(uploadedfilepath, flvfilepath)
	self.getthumbnail(uploadedfilepath, thumbnailpath)
	
	query = session.query(Movie)

	insertedmovie = query.get_by(movieid=insertedid)
	insertedmovie.source = flvfilepath
	insertedmovie.thumb = thumbnailpath

	session.flush()
		
    	return dict(movieid=str(insertedid))

    def converttoflv(self, inpath, outpath):
    	cmd = "ffmpeg -v 0 -i %s -ar 11025 %s 2>&1"%(inpath, outpath)
   	os.system(cmd)
   	
    def getthumbnail(self, inpath, outpath):
   	cmd = "ffmpeg -vframes 1 -i %s -pix_fmt rgb24 %s 2>&1"%(inpath, outpath)
   	os.system(cmd)
   
    def preminsert(self, title):
    	"""A preliminary insert method so that we can
    	   get the generated id.
    		Parameters: 
    			title - title for the movie
    		Return: Generated id    
	"""
    
	stmnt = movie_table.insert()
	res = stmnt.execute(title= title, width=300, height=200)
	return res.last_inserted_ids()[0]	
	
This sets the template that will carry the success and generated id of this conversion process. Now we need to create this template. So we create the upload.html template with the following code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>

<title>Video Sharing Site</title>

</head>

<body>
Successfully converted movie with id $movieid
</body>

</html>
Now let's test the application. Start up the server again if you didn't leave it on and upload an avi. Visit http://localhost:8080 and you should see there the upload form. So go ahead and upload an avi file and once this is done, it should re-direct you to another page that informs you of the assigned movie id. Next, we need to create the flex interface. Create and build the simplemovie.mxml in the moviestg directory by compiling the following code with the -use-network=false flag incase you're deploying on your local computer.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:VBox backgroundColor="white" width="400" height="335">
  <mx:VideoDisplay width="400" height="300" id="videoPlayer"
    source="{Application.application.parameters.movie}" />
  <mx:HBox width="100%" horizontalAlign="center">
    <mx:Button label="Play" click="videoPlayer.play()" />
  </mx:HBox>
</mx:VBox>
</mx:Application>
Now let's create the page that hosts the swf. Copy the following markup into the simptest.html template:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/">

<head>

<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>

<title>Video Sharing Site</title>

</head>

<body>
	<table>
  		<tr><td valign="top">
  			<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="400" height="335" codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
  			<param name="movie" value="simplemovie" />
  			<param name="quality" value="high" />
  			<param name="flashVars" value="movie=showmovie?movieid=$movieid"/>
  			<embed src="simplemovie" quality="high" width="400" height="335" play="true" loop="false" flashVars="movie=showmovie?movieid=$movieid" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer">
			</embed>
			</object>
		</td>
		<td valign="top">
			<ul>
				<li py:for="movie in movies"><a href="simptest?movieid=${movie.movieid}">${movie.title}</a></li>
			</ul>
		</td></tr>
	</table>
</body>

</html>
Next, we need to create the simplemovie method which would server the swf and the showmovie method which would serve the selected movie for the swf. Add the following code to the controller:
    @expose(template="genshi:moviestg.templates.simptest")
    def simptest(self, movieid = None):
	query = session.query(Movie)
	selectedmovies = query.select()
	
	return dict(movieid = movieid, movies = selectedmovies)
		
		
    @expose()
    def simplemovie(self):
    	executionpath = os.getcwd()
    	return serveFile(executionpath + "/simplemovie.swf")
    	
    @expose()
    def showmovie(self, movieid = None):
    	if( movieid != None):
    		query = session.query(Movie)
		reqmovie = query.get_by(movieid=movieid)
		
		if(reqmovie != None):
			return serveFile(reqmovie.source)
		else:
			return dict()
	else:
		return dict()
Now you can go ahead and test the application again. Upload a few more videos, and then visit the simptest address(http://localhost:8080/simptest). This is enough to show off Python and TurboGears and hopefully get you to consider them in your projects. The original tutorial continues on to show off flex heavily, which would warrant a tutorial on its own. But for the sake of completeness i will complete the tutorial in part2.