gmyth-stream/server/0.2/lib/server.py
author melunko
Fri Apr 20 03:58:43 2007 +0100 (2007-04-20)
branchtrunk
changeset 579 3fe481998d73
parent 574 2dfea125c76c
child 585 a1783dab9ba6
permissions -rw-r--r--
[svn r584] Added samples/gmyth-cat. Currently working only remote files (not livetv)
morphbr@565
     1
#!/usr/bin/env python
morphbr@565
     2
morphbr@565
     3
__author__ = "Gustavo Sverzut Barbieri / Artur Duque de Souza"
morphbr@565
     4
__author_email__ = "barbieri@gmail.com / artur.souza@indt.org.br"
morphbr@565
     5
__license__ = "GPL"
morphbr@565
     6
__version__ = "0.3"
morphbr@565
     7
morphbr@565
     8
import os
morphbr@565
     9
import threading
morphbr@565
    10
import SocketServer
morphbr@565
    11
import BaseHTTPServer
morphbr@565
    12
import socket
morphbr@565
    13
import urlparse
morphbr@565
    14
import cgi
morphbr@565
    15
import lib.utils as utils
morphbr@565
    16
import logging as log
morphbr@565
    17
morphbr@565
    18
__all__ = ("Transcoder", "RequestHandler", "Server", "serve_forever",
morphbr@565
    19
           "load_plugins_transcoders")
morphbr@565
    20
morphbr@565
    21
class Transcoder(object):
morphbr@577
    22
    log = log.getLogger("gms.transcoder")
morphbr@565
    23
    priority = 0   # negative values have higher priorities
morphbr@565
    24
    name = None # to be used in requests
morphbr@565
    25
morphbr@565
    26
    def __init__(self, params):
morphbr@565
    27
        self.params = params
morphbr@565
    28
    # __init__()
morphbr@565
    29
morphbr@565
    30
morphbr@565
    31
    def params_first(self, key, default=None):
morphbr@565
    32
        if default is None:
morphbr@565
    33
            return self.params[key][0]
morphbr@565
    34
        else:
morphbr@565
    35
            try:
morphbr@565
    36
                return self.params[key][0]
morphbr@565
    37
            except:
morphbr@565
    38
                return default
morphbr@565
    39
    # params_first()
morphbr@565
    40
morphbr@565
    41
morphbr@565
    42
    def get_mimetype(self):
morphbr@565
    43
        mux = self.params_first("mux", "mpg")
morphbr@565
    44
morphbr@565
    45
        if mux == "mpeg":
morphbr@565
    46
            return "video/mpeg"
morphbr@565
    47
        elif mux == "avi":
morphbr@565
    48
            return "video/x-msvideo"
morphbr@565
    49
        else:
morphbr@565
    50
            return "application/octet-stream"
morphbr@565
    51
    # get_mimetype()
morphbr@565
    52
morphbr@565
    53
morphbr@565
    54
    def start(self, outfile):
morphbr@565
    55
        return True
morphbr@565
    56
    # start()
morphbr@565
    57
morphbr@565
    58
morphbr@565
    59
    def stop(self):
morphbr@565
    60
        return Tru
morphbr@565
    61
    # stop()
morphbr@565
    62
morphbr@565
    63
morphbr@565
    64
    def __str__(self):
morphbr@577
    65
        return '%s("%s", mux="%s", params=%s, addr=%s)' % \
morphbr@565
    66
               (self.__class__.__name__,
morphbr@565
    67
                self.params_first("uri", "None"),
morphbr@565
    68
                self.params_first("mux", "mpg"),
morphbr@577
    69
                self.params,
morphbr@577
    70
                repr(self))
morphbr@565
    71
    # __str__()
morphbr@565
    72
# Transcoder
morphbr@565
    73
morphbr@565
    74
morphbr@565
    75
morphbr@565
    76
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
morphbr@577
    77
    log = log.getLogger("gms.request")
morphbr@565
    78
    def_transcoder = None
morphbr@565
    79
    transcoders = utils.PluginSet(Transcoder)
morphbr@565
    80
morphbr@565
    81
    @classmethod
morphbr@565
    82
    def load_plugins_transcoders(cls, directory):
morphbr@565
    83
        cls.transcoders.load_from_directory(directory)
morphbr@565
    84
morphbr@565
    85
        if cls.def_transcoder is None and cls.transcoders:
morphbr@565
    86
            cls.def_transcoder = cls.transcoders[0].name
morphbr@565
    87
    # load_plugins_transcoders()
morphbr@565
    88
morphbr@565
    89
morphbr@565
    90
    def do_dispatch(self, body):
morphbr@565
    91
        self.url = self.path
morphbr@565
    92
morphbr@565
    93
        pieces = urlparse.urlparse(self.path)
morphbr@565
    94
        self.path = pieces[2]
morphbr@565
    95
        self.query = cgi.parse_qs(pieces[4])
morphbr@565
    96
morphbr@565
    97
        if self.path == "/":
morphbr@565
    98
            self.serve_main(body)
morphbr@565
    99
        elif self.path == "/shutdown.do":
morphbr@565
   100
            self.serve_shutdown(body)
morphbr@565
   101
        elif self.path == "/stop-transcoder.do":
morphbr@565
   102
            self.serve_stop_transcoder(body)
morphbr@565
   103
        elif self.path == "/status.do":
morphbr@565
   104
            self.serve_status(body)
morphbr@565
   105
        elif self.path == "/play.do":
morphbr@565
   106
            self.serve_play(body)
morphbr@565
   107
        elif self.path == "/stream.do":
morphbr@565
   108
            self.serve_stream(body)
morphbr@565
   109
        else:
morphbr@565
   110
            self.send_error(404, "File not found")
morphbr@565
   111
    # do_dispatch()
morphbr@565
   112
morphbr@565
   113
morphbr@565
   114
    def do_GET(self):
morphbr@565
   115
        self.do_dispatch(True)
morphbr@565
   116
    # do_GET()
morphbr@565
   117
morphbr@565
   118
morphbr@565
   119
    def do_HEAD(self):
morphbr@565
   120
        self.do_dispatch(False)
morphbr@565
   121
    # do_HEAD()
morphbr@565
   122
morphbr@565
   123
morphbr@565
   124
    def _nav_items(self):
morphbr@565
   125
        self.wfile.write("""\
morphbr@565
   126
   <li><a href="/play.do">Play</a></li>
morphbr@565
   127
   <li><a href="/status.do">Status</a></li>
morphbr@565
   128
   <li><a href="/stop-transcoder.do">Stop transcoders</a></li>
morphbr@565
   129
   <li><a href="/shutdown.do">Shutdown Server</a></li>
morphbr@565
   130
""")
morphbr@565
   131
    # _nav_items()
morphbr@565
   132
morphbr@565
   133
morphbr@565
   134
    def serve_main(self, body):
morphbr@565
   135
        self.send_response(200)
morphbr@565
   136
        self.send_header("Content-Type", "text/html")
morphbr@565
   137
        self.send_header('Connection', 'close')
morphbr@565
   138
        self.end_headers()
morphbr@565
   139
        if body:
morphbr@565
   140
            self.wfile.write("""\
morphbr@565
   141
<html>
morphbr@569
   142
   <head><title>GMyth-Streamer Server</title></head>
morphbr@565
   143
   <body>
morphbr@569
   144
<h1>Welcome to GMyth-Streamer Server</h1>
morphbr@565
   145
<ul>
morphbr@565
   146
""")
morphbr@565
   147
            self._nav_items()
morphbr@565
   148
            self.wfile.write("""\
morphbr@565
   149
</ul>
morphbr@565
   150
   </body>
morphbr@565
   151
</html>
morphbr@565
   152
""")
morphbr@565
   153
    # serve_main()
morphbr@565
   154
morphbr@565
   155
morphbr@565
   156
    def serve_play(self, body):
morphbr@565
   157
        self.send_response(200)
morphbr@565
   158
        self.send_header("Content-Type", "text/html")
morphbr@565
   159
        self.send_header('Connection', 'close')
morphbr@565
   160
        self.end_headers()
morphbr@565
   161
        if body:
morphbr@565
   162
            self.wfile.write("""\
morphbr@565
   163
<html>
morphbr@569
   164
   <head><title>GMyth-Streamer Server</title></head>
morphbr@565
   165
   <body>
morphbr@565
   166
   <h1>Play</h1>
morphbr@565
   167
   <form action="/stream.do" method="GET">
morphbr@565
   168
      <input type="text" name="type" value="file" />://<input type="text" name="location" value=""/>
morphbr@565
   169
      <input type="submit" />
morphbr@565
   170
   </form>
morphbr@565
   171
   <ul>
morphbr@565
   172
""")
morphbr@565
   173
            self._nav_items()
morphbr@565
   174
            self.wfile.write("""\
morphbr@565
   175
   </ul>
morphbr@565
   176
   </body>
morphbr@565
   177
</html>
morphbr@565
   178
""")
morphbr@565
   179
    # serve_play()
morphbr@565
   180
morphbr@565
   181
morphbr@565
   182
    def serve_shutdown(self, body):
morphbr@565
   183
        self.send_response(200)
morphbr@565
   184
        self.send_header("Content-Type", "text/html")
morphbr@565
   185
        self.send_header('Connection', 'close')
morphbr@565
   186
        self.end_headers()
morphbr@565
   187
        if body:
morphbr@565
   188
            self.wfile.write("""\
morphbr@565
   189
<html>
morphbr@569
   190
   <head><title>GMyth-Streamer Server Exited</title></head>
morphbr@565
   191
   <body>
morphbr@569
   192
      <h1>GMyth-Streamer is not running anymore</h1>
morphbr@565
   193
   </body>
morphbr@565
   194
</html>
morphbr@565
   195
""")
morphbr@565
   196
        self.server.server_close()
morphbr@565
   197
    # serve_shutdown()
morphbr@565
   198
morphbr@565
   199
morphbr@565
   200
    def serve_stop_all_transcoders(self, body):
morphbr@565
   201
        self.send_response(200)
morphbr@565
   202
        self.send_header("Content-Type", "text/html")
morphbr@565
   203
        self.send_header('Connection', 'close')
morphbr@565
   204
        self.end_headers()
morphbr@565
   205
        if body:
morphbr@565
   206
            self.server.stop_transcoders()
morphbr@565
   207
            self.wfile.write("""\
morphbr@565
   208
<html>
morphbr@569
   209
   <head><title>GMyth-Streamer Server Stopped Transcoders</title></head>
morphbr@565
   210
   <body>
morphbr@569
   211
      <h1>GMyth-Streamer stopped running transcoders</h1>
morphbr@565
   212
      <ul>
morphbr@565
   213
""")
morphbr@565
   214
            self._nav_items()
morphbr@565
   215
            self.wfile.write("""\
morphbr@565
   216
      </ul>
morphbr@565
   217
   </body>
morphbr@565
   218
</html>
morphbr@565
   219
    """)
morphbr@565
   220
    # serve_stop_all_transcoders()
morphbr@565
   221
morphbr@565
   222
morphbr@565
   223
    def serve_stop_selected_transcoders(self, body, requests):
morphbr@565
   224
        self.send_response(200)
morphbr@565
   225
        self.send_header("Content-Type", "text/html")
morphbr@565
   226
        self.send_header('Connection', 'close')
morphbr@565
   227
        self.end_headers()
morphbr@565
   228
        if body:
morphbr@565
   229
            self.wfile.write("""\
morphbr@565
   230
<html>
morphbr@569
   231
   <head><title>GMyth-Streamer Server Stopped Transcoders</title></head>
morphbr@565
   232
   <body>
morphbr@569
   233
      <h1>GMyth-Streamer stopped running transcoders:</h1>
morphbr@565
   234
      <ul>
morphbr@565
   235
    """)
morphbr@565
   236
            transcoders = self.server.get_transcoders()
morphbr@565
   237
morphbr@565
   238
            for req in requests:
morphbr@565
   239
                try:
morphbr@565
   240
                    host, port = req.split(":")
morphbr@565
   241
                except IndexError:
morphbr@565
   242
                    continue
morphbr@565
   243
morphbr@565
   244
                port = int(port)
morphbr@565
   245
                addr = (host, port)
morphbr@565
   246
morphbr@565
   247
                for t, r in transcoders:
morphbr@565
   248
                    if r.client_address == addr:
morphbr@571
   249
                        try:
morphbr@571
   250
                            t.stop()
morphbr@571
   251
                        except Exception, e:
morphbr@571
   252
                            self.log.info("Plugin already stopped")
morphbr@571
   253
morphbr@565
   254
                        self.wfile.write("""\
morphbr@565
   255
         <li>%s: %s:%s</li>
morphbr@565
   256
""" % (t, addr[0], addr[1]))
morphbr@565
   257
                        break
morphbr@565
   258
            self.wfile.write("""\
morphbr@565
   259
      </ul>
morphbr@565
   260
      <ul>
morphbr@565
   261
""")
morphbr@565
   262
            self._nav_items()
morphbr@565
   263
            self.wfile.write("""\
morphbr@565
   264
      </ul>
morphbr@565
   265
   </body>
morphbr@565
   266
</html>
morphbr@565
   267
""")
morphbr@565
   268
    # serve_stop_selected_transcoders()
morphbr@565
   269
morphbr@565
   270
morphbr@565
   271
    def serve_stop_transcoder(self, body):
morphbr@565
   272
        req = self.query.get("request", None)
morphbr@565
   273
        if req and "all" in req:
morphbr@565
   274
            self.serve_stop_all_transcoders(body)
morphbr@565
   275
        elif req:
morphbr@565
   276
            self.serve_stop_selected_transcoders(body, req)
morphbr@565
   277
        else:
morphbr@565
   278
            self.serve_status(body)
morphbr@565
   279
    # serve_stop_transcoder()
morphbr@565
   280
morphbr@565
   281
morphbr@565
   282
    def serve_status(self, body):
morphbr@565
   283
        self.send_response(200)
morphbr@565
   284
        self.send_header("Content-Type", "text/html")
morphbr@565
   285
        self.send_header('Connection', 'close')
morphbr@565
   286
        self.end_headers()
morphbr@565
   287
        if body:
morphbr@565
   288
            self.wfile.write("""\
morphbr@565
   289
<html>
morphbr@569
   290
   <head><title>GMyth-Streamer Server Status</title></head>
morphbr@565
   291
   <body>
morphbr@569
   292
      <h1>GMyth-Streamer Status</h1>
morphbr@565
   293
""")
morphbr@565
   294
            tl = self.server.get_transcoders()
morphbr@565
   295
            if not tl:
morphbr@565
   296
                self.wfile.write("<p>No running transcoder.</p>\n")
morphbr@565
   297
            else:
morphbr@565
   298
                self.wfile.write("<p>Running transcoders:</p>\n")
morphbr@565
   299
                self.wfile.write("""\
morphbr@565
   300
      <ul>
morphbr@565
   301
         <li><a href="/stop-transcoder.do?request=all">[STOP ALL]</a></li>
morphbr@565
   302
""")
morphbr@565
   303
                for transcoder, request in tl:
morphbr@565
   304
                    self.wfile.write("""\
morphbr@565
   305
      <li>%s: %s:%s <a href="/stop-transcoder.do?request=%s:%s">[STOP]</a></li>
morphbr@565
   306
""" % (transcoder, request.client_address[0], request.client_address[1],
morphbr@565
   307
       request.client_address[0], request.client_address[1]))
morphbr@565
   308
morphbr@565
   309
                self.wfile.write("""\
morphbr@565
   310
      </ul>
morphbr@565
   311
      <ul>
morphbr@565
   312
""")
morphbr@565
   313
            self._nav_items()
morphbr@565
   314
            self.wfile.write("""\
morphbr@565
   315
      </ul>
morphbr@565
   316
   </body>
morphbr@565
   317
</html>
morphbr@565
   318
""")
morphbr@565
   319
    # serve_status()
morphbr@565
   320
morphbr@565
   321
morphbr@565
   322
    def _get_transcoder(self):
morphbr@565
   323
        # get transcoder option: mencoder is the default
morphbr@565
   324
        request_transcoders = self.query.get("transcoder", ["mencoder"])
morphbr@565
   325
morphbr@565
   326
        for t in request_transcoders:
morphbr@565
   327
            transcoder = self.transcoders.get(t)
morphbr@565
   328
            if transcoder:
morphbr@565
   329
                return transcoder
morphbr@565
   330
morphbr@565
   331
        if not transcoder:
morphbr@565
   332
            return self.transcoders[self.def_transcoder]
morphbr@565
   333
    # _get_transcoder()
morphbr@565
   334
morphbr@565
   335
morphbr@565
   336
    def serve_stream(self, body):
morphbr@565
   337
        transcoder = self._get_transcoder()
morphbr@565
   338
        try:
morphbr@572
   339
            obj = transcoder(self.query)
morphbr@565
   340
        except Exception, e:
morphbr@565
   341
            self.send_error(500, str(e))
morphbr@565
   342
            return
morphbr@565
   343
morphbr@565
   344
        self.send_response(200)
morphbr@565
   345
        self.send_header("Content-Type", obj.get_mimetype())
morphbr@565
   346
        self.send_header('Connection', 'close')
morphbr@565
   347
        self.end_headers()
morphbr@565
   348
morphbr@565
   349
        if body:
morphbr@565
   350
            self.server.add_transcoders(self, obj)
morphbr@565
   351
            obj.start(self.wfile)
morphbr@565
   352
            self.server.del_transcoders(self, obj)
morphbr@565
   353
    # serve_stream()
morphbr@565
   354
morphbr@565
   355
morphbr@565
   356
    def log_request(self, code='-', size='-'):
morphbr@565
   357
        self.log.info('"%s" %s %s', self.requestline, str(code), str(size))
morphbr@565
   358
    # log_request()
morphbr@565
   359
morphbr@565
   360
morphbr@565
   361
    def log_error(self, format, *args):
morphbr@565
   362
        self.log.error("%s: %s" % (self.address_string(), format % args))
morphbr@565
   363
    # log_error()
morphbr@565
   364
morphbr@565
   365
morphbr@565
   366
    def log_message(self, format, *args):
morphbr@565
   367
        self.log.info("%s: %s" % (self.address_string(), format % args))
morphbr@565
   368
    # log_message()
morphbr@565
   369
# RequestHandler
morphbr@565
   370
morphbr@565
   371
morphbr@565
   372
morphbr@565
   373
class Server(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
morphbr@577
   374
    log = log.getLogger("gms.server")
morphbr@565
   375
    run = True
morphbr@565
   376
    _transcoders = {}
morphbr@565
   377
    _lock = threading.RLock()
morphbr@565
   378
morphbr@565
   379
    def serve_forever(self):
morphbr@565
   380
        self.log.info("GMyth-Streamer serving HTTP on %s:%s" %
morphbr@565
   381
                      self.socket.getsockname())
morphbr@565
   382
        try:
morphbr@565
   383
            while self.run:
morphbr@565
   384
                self.handle_request()
morphbr@565
   385
        except KeyboardInterrupt, e:
morphbr@565
   386
            pass
morphbr@565
   387
morphbr@565
   388
        self.log.debug("Stopping all remaining transcoders...")
morphbr@565
   389
        self.stop_transcoders()
morphbr@565
   390
        self.log.debug("Transcoders stopped!")
morphbr@565
   391
    # serve_forever()
morphbr@565
   392
morphbr@565
   393
morphbr@565
   394
    def get_request(self):
morphbr@565
   395
        skt = self.socket
morphbr@565
   396
        old = skt.gettimeout()
morphbr@565
   397
        skt.settimeout(0.5)
morphbr@565
   398
        while self.run:
morphbr@565
   399
            try:
morphbr@565
   400
                r = skt.accept()
morphbr@565
   401
                skt.settimeout(old)
morphbr@565
   402
                return r
morphbr@565
   403
            except socket.timeout, e:
morphbr@565
   404
                pass
morphbr@565
   405
        raise socket.error("Not running")
morphbr@565
   406
    # get_request()
morphbr@565
   407
morphbr@565
   408
morphbr@565
   409
    def server_close(self):
morphbr@565
   410
        self.run = False
morphbr@565
   411
        self.stop_transcoders()
morphbr@565
   412
morphbr@565
   413
        BaseHTTPServer.HTTPServer.server_close(self)
morphbr@565
   414
    # server_close()
morphbr@565
   415
morphbr@565
   416
morphbr@565
   417
    def stop_transcoders(self):
morphbr@565
   418
        self._lock.acquire()
morphbr@565
   419
        for transcoder, request in self._transcoders.iteritems():
morphbr@565
   420
            self.log.info("Stop transcoder: %s, client=%s" %
morphbr@565
   421
                          (transcoder, request.client_address))
morphbr@565
   422
            transcoder.stop()
morphbr@565
   423
        self._lock.release()
morphbr@565
   424
    # stop_transcoders()
morphbr@565
   425
morphbr@565
   426
morphbr@565
   427
    def get_transcoders(self):
morphbr@565
   428
        self._lock.acquire()
morphbr@565
   429
        try:
morphbr@565
   430
            return self._transcoders.items()
morphbr@565
   431
        finally:
morphbr@565
   432
            self._lock.release()
morphbr@565
   433
    # get_transcoders()
morphbr@565
   434
morphbr@565
   435
morphbr@565
   436
    def add_transcoders(self, request, transcoder):
morphbr@565
   437
        self._lock.acquire()
morphbr@565
   438
        try:
morphbr@565
   439
            self._transcoders[transcoder] = request
morphbr@565
   440
        finally:
morphbr@565
   441
            self._lock.release()
morphbr@565
   442
    # add_transcoders()
morphbr@565
   443
morphbr@565
   444
morphbr@565
   445
    def del_transcoders(self, request, transcoder):
morphbr@565
   446
        self._lock.acquire()
morphbr@565
   447
        try:
morphbr@565
   448
            del self._transcoders[transcoder]
morphbr@565
   449
        finally:
morphbr@565
   450
            self._lock.release()
morphbr@565
   451
    # del_transcoders()
morphbr@565
   452
# Server
morphbr@565
   453
morphbr@565
   454
morphbr@565
   455
morphbr@565
   456
def serve_forever(host="0.0.0.0", port=40000):
morphbr@565
   457
    addr = (host, port)
morphbr@565
   458
morphbr@565
   459
    RequestHandler.protocol_version = "HTTP/1.0"
morphbr@565
   460
    httpd = Server(addr, RequestHandler)
morphbr@565
   461
    httpd.serve_forever()
morphbr@565
   462
# serve_forever()
morphbr@565
   463
morphbr@565
   464
morphbr@565
   465
def load_plugins_transcoders(directory):
morphbr@565
   466
    RequestHandler.load_plugins_transcoders(directory)
morphbr@565
   467
# load_plugins_transcoders()