Network programming for PyS60 (XVI)
by Marcelo Barros
Qik is a new and innovative service that allows you stream video live from your cell phone to the web. These videos can be shared with your friends and they are available for viewing and download. Qik has a straight relationship with other on-line services, like twitter, Youtube, Facebook and wordpress. In this post an API would be presented for browsing public streams for some Qik account and an S60 application for displaying these videos, both written in Python. Even though it is not complete, this API is an excellent start point for new services and programs based on Qik.
Current Qik API is based on JSON-RPC and REST (only available for the Qik-ly API and not used in this post). JSON-RPC Qik API uses HTTP for transfer data between client and Qik engine. For instance, the public streams of Qik user marcelobarrosalmeida can be retrieved with the following HTTP data:
POST /api/jsonrpc?apikey=YOUR_API_KEY HTTP/1.0 Content-Length: 80 Host: engine.qik.com Content-Type: application/json; charset=UTF-8 {"method": "qik.stream.public_user_streams","params": ["marcelobarrosalmeida"]}
The response is below:
HTTP/1.1 200 OK Server: nginx/0.7.59 Date: Fri, 07 Aug 2009 14:31:06 GMT Content-Type: text/json Connection: close Content-Length: 1706 X-Qik-Origin: 229 [[{"url": "http://qik.com/video/2363785", "live": false, "user_id": 365150, "small_thumbnail_url": "http://media.qik.com/media.thumbnails.128/2a7c74d9862b4f2caa9ea6d7296b9225.jpg", "title": "Untitled", "duration": 54, "created_at": "2009-07-31 13:23:30", "views": 7, "id": 2363785}, {"url": "http://qik.com/video/2366002", "live": false, "user_id": 365150, "small_thumbnail_url": "http://media.qik.com/media.thumbnails.128/93befe9eb93c464aadbd92d8ea16ac77.jpg", "title": "Pint da Guinness", "duration": 30, "created_at": "2009-07-31 18:15:29", "views": 18, "id": 2366002}, {"url": "http://qik.com/video/2363998", "live": false, "user_id": 365150, "small_thumbnail_url": "http://media.qik.com/media.thumbnails.128/566d3f87915b46479ad72eff0c0a21ca.jpg", "title": "Trafalgar square", "duration": 41, "created_at": "2009-07-31 14:01:39", "views": 11, "id": 2363998}, {"url": "http://qik.com/video/2357498", "live": false, "user_id": 365150, "small_thumbnail_url": "http://media.qik.com/media.thumbnails.128/cd4e173774884249968e757d364fe34d.jpg", "title": "Untitled", "duration": 55, "created_at": "2009-07-30 21:06:58", "views": 5, "id": 2357498}, {"url": "http://qik.com/video/2356796", "live": false, "user_id": 365150, "small_thumbnail_url": "http://media.qik.com/media.thumbnails.128/2053be143c2c4e708b9bcd51d62a7359.jpg", "title": "Untitled", "duration": 97, "created_at": "2009-07-30 19:54:10", "views": 7, "id": 2356796}, {"url": "http://qik.com/video/2363619", "live": false, "user_id": 365150, "small_thumbnail_url": "http://media.qik.com/media.thumbnails.128/d230dcf8cf3e4f9fb6980f512b66265c.jpg", "title": "Untitled", "duration": 61, "created_at": "2009-07-31 12:54:22", "views": 5, "id": 2363619}]]
Responses uses JSON as well and it will vary depending on required command. Check Qik API for details and this article for a good introduction about object serialization and JSON.
Using simplejson and urllib it is possible to implement an API for accessing Qik services. Since it is necessary to include special HTTP headers, instead using urllib.urlopen, we need to create an URL opener (using urllib.URLopener), adding required headers to it:
urlopener = urllib.URLopener() urlopener.addheaders = [('Host','engine.qik.com'), ('Content-Type','application/json; charset=UTF-8')]
The desired remote procedure call can be reached using the open() method:
data = json.dumps(data) url = 'http://engine.qik.com/api/jsonrpc?apikey=YOUR_API_KEY' f = urlopener.open(url,data)
Finally, using the file descriptor returned by open(), it is possible to get the response and decode it:
data = json.loads(f.read())[0]
The API code is below and it can be run in PyS60 1.4.x or 1.9.x and probably in any version of Python for PC with urllib. But you will need an API key for using it. Request it at Qik API page.
# -*- coding: utf-8 -*- # Marcelo Barros de Almeida # marcelobarrosalmeida (at) gmail.com # License: GPL3 import simplejson as json import urllib class QikApi(object): """ Simple class for Qik videos proxy support """ def __init__(self, api_key, qik_usr): """ Create a new Qik API instance with given API key and user name """ self.qik_url = 'http://engine.qik.com/api/jsonrpc?apikey=' + api_key self.qik_usr = qik_usr self.qik_id = -1 def __urlopener(self): """ Return an urlopener with Qik required headers already set """ urlopener = urllib.URLopener() urlopener.addheaders = [('Host','engine.qik.com'), ('Content-Type','application/json; charset=UTF-8')] return urlopener def __open(self,url,params=""): """ Open a given URL using GET or POST and Qik headers """ if params: f = self.__urlopener().open(url,params) #post else: f = self.__urlopener().open(url) #get return f def __qik_request(self,data): """ Qik request. Encode data in json format, do the request and decode json response """ data = json.dumps(data) f = self.__open(self.qik_url,data) res = json.loads(f.read())[0] return res def __check_id(self,qik_id): """ Check if user ID was retrieved or not. If not, download it """ if qik_id == -1: if self.qik_id == -1: self.qik_id = self.get_user_public_profile()[u'id'] qik_id = self.qik_id return qik_id def get_public_user_streams(self,usr=''): """ Return all public stream for a given user (or for the current user, if it not provided) """ if not usr: usr = self.qik_usr data = {'method': 'qik.stream.public_user_streams','params': [usr]} return self.__qik_request(data) def get_user_public_profile(self,usr=''): """ Return public profile for a given user (or for the current user, if it not provided) """ if not usr: usr = self.qik_usr data = {'method': 'qik.user.public_profile','params': [usr]} return self.__qik_request(data) def get_user_public_detailed_profile(self,usr=''): """ Return detailed public profile for a given user (or for the current user, if it not provided) """ if not usr: usr = self.qik_usr data = {'method': 'qik.user.public_detailed_profile','params': [usr]} return self.__qik_request(data) def get_user_followers(self,qik_id=-1): """ Return the list of followers for a given user (or for the current user, if it not provided) """ qik_id = self.__check_id(qik_id) data = {'method': 'qik.user.followers','params': [qik_id]} return self.__qik_request(data) def get_user_following(self,qik_id=-1): """ Return the list of following for a given user (or for the current user, if it not provided) """ qik_id = self.__check_id(qik_id) data = {'method': 'qik.user.following','params': [qik_id]} return self.__qik_request(data) def get_public_stream_info(self,vid_id): """ Get detailed information about some public video """ data = {'method': 'qik.stream.public_info','params': [vid_id]} return self.__qik_request(data)
Using the Basic framework for creating user interface it is simple to create a demo application for showing user streams and their Qik following/followers, each one in a different tab. For playing the videos (Flash-lite capable device is required) a local HTML with links to the embedded video is created (see QIK_TEMPLATE variable) and the native web browser is called to show it.
You can see this application in action in the following video.
Check the program repository for newer versions and updates. For running it, just copy all files (window.py, qikapi.py, qikview.py, simplejson.py) to your memory card (into e:Python) and use the Python interpreter to execute qikview.py. A zip package is available here.
# -*- coding: utf-8 -*- # Marcelo Barros de Almeida # marcelobarrosalmeida (at) gmail.com # License: GPL3 import sys sys.path.append('e:Python') import window from appuifw import * from qikapi import QikApi import time API_KEY = 'YOUR_API_KEY' QIK_TEMPLATE = u""" __TITLE__ <object width="220" height="185" data="http://qik.com/swfs/qik_player_lite.swf?file=http://qik.com/flv/__FILENAME__.flv&thumbnail=http://qik.com/redir/__FILENAME__.jpg&size=false&aplay=true&autorew=false&layout=small&title=__TITLE__" type="application/x-shockwave-flash"><param name="id" value="player" /><param name="align" value="middle" /><param name="menu" value="false" /><param name="quality" value="high" /><param name="bgcolor" value="#999999" /><param name="src" value="http://qik.com/swfs/qik_player_lite.swf?file=http://qik.com/flv/__FILENAME__.flv&thumbnail=http://qik.com/redir/__FILENAME__.jpg&size=false&aplay=true&autorew=false&layout=small&title=__TITLE__" /><param name="name" value="player" /></object>""".encode('utf-8') class QikView(window.Application): def __init__(self): self.qik_usr = u"" self.qik_api = None self.data = {'profile':[], 'streams':[], 'followers':[], 'following':[]} # menus streams_menu = [(u"Show stream",self.show_video)] common_menu = [(u"Update",self.update), (u"Setup",self.setup), (u"About",self.about)] # bodies self.streams = Listbox([(u"Please, setup and update",u"")],self.show_video) self.following = Listbox([(u"Please, setup and update",u"")]) self.followers = Listbox([(u"Please, setup and update",u"")]) window.Application.__init__(self, u"Qik View", [(u"Streams",self.streams,streams_menu), (u"Following",self.following,[]), (u"Followers",self.followers,[])], common_menu) def update(self): if not self.qik_usr or not self.qik_api: note(u"Please, setup the Qik user",u"error") else: self.lock_ui() try: self.set_title(u"Updating profile...") self.data['profile'] = self.qik_api.get_user_public_profile() self.set_title(u"Updating streams...") self.data['streams'] = self.qik_api.get_public_user_streams() self.set_title(u"Updating followers...") self.data['followers'] = self.qik_api.get_user_followers() self.set_title(u"Updating following...") self.data['following'] = self.qik_api.get_user_following() except: note(u"Network error. Please, try again","error") else: self.update_bodies() self.set_title(u"Qik View") self.unlock_ui() self.refresh() def update_bodies(self): streams = [] followers = [] following = [] for s in self.data['streams']: h1 = s['title'] + (u" (%ds)" % s['duration']) h2 = s['created_at'] streams.append((h1,h2)) for f in self.data['followers']: followers.append((f[u'username'],f[u'full_name'])) for f in self.data['following']: following.append((f[u'username'],f[u'full_name'])) if streams: self.streams.set_list(streams) else: self.streams.set_list([(u"No streams available",u"")]) if followers: self.followers.set_list(followers) else: self.followers.set_list([(u"No followers available",u"")]) if following: self.following.set_list(following) else: self.following.set_list([(u"No following available",u"")]) def setup(self): usr = query(u"Qik user:","text",self.qik_usr) if usr is not None: self.qik_usr = usr self.qik_api = QikApi(API_KEY,self.qik_usr) def show_video(self): if self.data['streams']: # retrieve information about video idx = self.streams.current() if not self.data['streams'][idx].has_key('stream_info'): vid = self.data['streams'][idx][u'id'] self.lock_ui(u"Downloading stream info...") try: self.data['streams'][idx]['stream_info'] = self.qik_api.get_public_stream_info(vid) except: note(u"Network error. Please, try again","error") ret = True else: ret = False self.set_title(u"Qik View") self.unlock_ui() self.refresh() if ret: return tit = self.data['streams'][idx]['stream_info'][u'title'].encode('utf-8') fn = self.data['streams'][idx]['stream_info'][u'filename'].encode('utf-8') html_code = QIK_TEMPLATE.replace('__FILENAME__',fn).replace('__TITLE__',tit) html_file = "html_" + time.strftime("%Y%m%d_%H%M%S", time.localtime()) + ".html" try: fp = open(html_file,"wt") fp.write(html_code) fp.close() except: note(u"Could not create HTML file","error") return viewer = Content_handler(self.refresh) try: viewer.open(html_file) except: note(u"Can not open browser","error") def about(self): note(u"Qik API for PyS60nby marcelobarrosalmeida@gmail.com",u"info") if __name__ == "__main__": app = QikView() app.run()
Related posts:
- Network programming for PyS60 (VIII) Did you do your homework ? So, I would like...
- Network programming for PyS60 (VII) Everything is about "protocols" in computer networks, doesn't it ?...
- Network programming for PyS60 (XII) Until now we have used only TCP in our examples...
- Network programming for PyS60 (XIV) Have you already heard about Beautiful Soup ? Beautiful Soup...
- Network programming for PyS60 (X) To celebrate our tenth post, I will talk about urllib...
Related posts brought to you by Yet Another Related Posts Plugin.