Doge log

Abby CTO 雑賀 力王のオフィシャルサイトです

Twistedで作るローカルYoutubeサーバ その5

まとめ。
まとめで全ソース。

server.tac
from twisted.application.internet import TimerService
from twisted.application import service, strports
from nevow import static
from nevow import appserver

import setting
import downloader
import web
import shell

application = service.Application('youtube')

page = web.ListPage()
page.putChild('flvplayer.swf', static.File('./flvplayer.swf'))
page.putChild('video', static.File(setting.repo))

site = appserver.NevowSite(page)
strports.service("8081", site).setServiceParent(application)
strports.service("2222", shell.getManholeFactory(globals(), admin='admin')).setServiceParent(application)
timer = TimerService(60, downloader.downloadAll)
timer.setServiceParent(application)
web.py
from nevow import rend
from nevow import loaders
from nevow import accessors
from nevow import inevow
from twisted.python import util
from twisted.python.components import registerAdapter

items = []

class VideoItem(object):
    pass

class ListPage(rend.Page):

    docFactory=loaders.xmlfile(util.sibpath(__file__, 'list.html'))
    
    def childFactory(self, ctx, segments):
        return DetailPage(segments)

    def data_list(self, ctx, data):
        return items

class DetailPage(rend.Page):
    addSlash = True
    docFactory=loaders.xmlfile(util.sibpath(__file__, 'detail.html'))
    
    def __init__(self, id):
        self.id = id

    def data_id(self, ctx, data):
        return '/flvplayer.swf?file=/video/%s.flv' % self.id

registerAdapter(accessors.ObjectContainer, VideoItem, inevow.IContainer)
list.html
<html xmlns:n="http://nevow.com/ns/nevow/0.1">
    <head>YouTube</head>
    <body>
        <ul n:render="sequence" n:data="list">
            <li n:pattern="item">
            <a><n:attr name="href">/<n:invisible n:data="id" n:render="data" />/</n:attr><n:invisible n:data="title" n:render="string" /></a>
            </li>
        </ul>
    </body>
</html>
detail.html
<html xmlns:n="http://nevow.com/ns/nevow/0.1">
    <head>Detail</head>
    <body>
        <object type="application/x-shockwave-flash" data="/flvplayer.swf?file=/video/4-uxh1iQX0E.flv" width="560" height="380" wmode="transparent"><n:attr name="data" n:data="id" n:render="string" />
            <param name="movie"><n:attr name="value" n:data="id" n:render="string" /></param>
            <param name="wmode" value="transparent" />
        </object>
    </body>
</html>
downloder.py
from twisted.web import client
from twisted.web import error
from twisted.internet import reactor

import os.path
import re

import web

const_video_url_params_re = re.compile(r'player2\.swf\?([^"]+)"', re.M)
const_video_url_real_str = 'http://www.youtube.com/get_video?%s'
const_video_url_re = re.compile(r'^((?:http://)?(?:\w+\.)?youtube\.com/(?:v/|(?:watch(?:\.php)?)?\?(?:.+&)?v=))?([0-9A-Za-z_-]+)(?(1)[&/].*)?$')
const_video_title_re = re.compile(r'<title>YouTube - ([^<]*)</title>', re.M | re.I)

def _finish(data, id, title, file):
    print 'finish %s %s %s' % (title, id, file)
    v = web.VideoItem()
    v.title = title
    v.id = id
    v.file = file
    web.items.append(v)

def _err(error):
    print error

def downloadFlv(url, file):
        
    def _parseFlvUrl(data):
        match = const_video_url_params_re.search(data)
        param = match.group(1)
        url =  const_video_url_real_str %  param
        match_title = const_video_title_re.search(data)
        title = match_title.group(1)
        return (url, title)
    
    def _download(file, id):
        if os.path.isdir(file):
            file = os.path.join(file, '%s.flv' % id)
        def _downloadPage(data):
            url, title = data
            client.downloadPage(url, file).addCallback(_finish, id, title, file).addErrback(_err)
        return _downloadPage

    match = const_video_url_re.search(url)
    id = match.group(2)

    return client.getPage(url).addCallback(_parseFlvUrl).addCallback(_download(file, id)).addErrback(_err)

def downloadAll():
    print 'start'
    import setting
    while setting.video:
        url = setting.video.pop()
        downloadFlv(url, setting.repo)
shell.py
from twisted.cred import portal, checkers
from twisted.conch import manhole, manhole_ssh

def getManholeFactory(namespace, **passwords):
    realm = manhole_ssh.TerminalRealm()
    def getManhole(_): 
        return manhole.Manhole(namespace)
    realm.chainedProtocolFactory.protocolFactory = getManhole
    p = portal.Portal(realm)
    p.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(**passwords))
    f = manhole_ssh.ConchFactory(p)
    return f
setting.py
repo = '/tmp'
video = []

とまあ長々と最低限の実装だけ書いてみた。
好み次第だけどdetail.htmlとかなしで一覧でガーっとみるのとかいいよなあっても気もする。
nevowは全体的に情報が少なめなのでソースを見るのが早かったりする。
詳しい情報はnevowのサイトを参照。
Twistedっていうとなんかprotocolとかどーのこーのみたいな感じを受けそうだけど上位なAPIが結構あったりするんで結構便利だと思う。
(開発用にXXXサーバが必要な時とかすぐ作っちゃえる)
と言っても絶対流行らないね。
nevowはもっと流行らない!
うくく。