gmyth-stream/server/plugins/media/mencoder.py
author morphbr
Thu Apr 12 15:12:12 2007 +0100 (2007-04-12)
branchtrunk
changeset 533 e55310730feb
parent 516 f353f0da6f07
child 534 cb1c3e2988b9
permissions -rw-r--r--
[svn r538] Bug fixes inside Gmyth-Stream
morphbr@533
     1
from __future__ import division
morphbr@533
     2
renatofilho@484
     3
import os
renatofilho@484
     4
import sys
renatofilho@484
     5
import lib
renatofilho@484
     6
import time
morphbr@516
     7
import shlex
renatofilho@484
     8
import signal
renatofilho@484
     9
import socket
renatofilho@484
    10
import ConfigParser
morphbr@516
    11
import logging as log
renatofilho@484
    12
renatofilho@484
    13
from select import *
renatofilho@484
    14
from subprocess import *
renatofilho@484
    15
morphbr@497
    16
class Media(object):
renatofilho@484
    17
renatofilho@484
    18
    def __init__(self, config):
renatofilho@484
    19
renatofilho@484
    20
        self.config = config
morphbr@533
    21
        self.do_cleanup()
morphbr@533
    22
morphbr@533
    23
    # __init__()
morphbr@533
    24
morphbr@533
    25
morphbr@533
    26
    def do_cleanup(self):
morphbr@516
    27
        self.path = ""
morphbr@516
    28
        self.args = []
morphbr@516
    29
        self.language = None
morphbr@516
    30
        self.subtitle = None
morphbr@516
    31
        self.mpegopts = None
renatofilho@484
    32
        self.socket = None
renatofilho@484
    33
        self.child_pid = None
renatofilho@484
    34
        self.mplayer = None
renatofilho@484
    35
        self.mencoder_pid = None
renatofilho@484
    36
        self.mplayer_pid = None
morphbr@533
    37
        self.audio_opts = None
morphbr@533
    38
        self.video_opts = None
morphbr@533
    39
        self.gst_pipe = None
morphbr@533
    40
        self.gst_pid = None
morphbr@533
    41
        self.transcode_local = None
renatofilho@484
    42
morphbr@533
    43
    # do_cleanup()
morphbr@533
    44
morphbr@514
    45
morphbr@516
    46
    def setup_opts(self, options):
renatofilho@484
    47
renatofilho@484
    48
        for opt in options:
renatofilho@484
    49
morphbr@504
    50
            if opt == "local":
morphbr@511
    51
                self.mplayer = lib.which("mplayer")
renatofilho@484
    52
morphbr@497
    53
            elif opt.find("language=") >= 0:
renatofilho@484
    54
                try:
morphbr@516
    55
                    lan = opt.split("=")[1]
morphbr@516
    56
                    if len(lan) < 2:
morphbr@516
    57
                        self.language = lan
morphbr@516
    58
                except Exception, e:
morphbr@533
    59
                    log.error("Bad language option: %s" % opt)
morphbr@516
    60
morphbr@516
    61
            elif opt.find("subtitle=") >= 0:
morphbr@516
    62
                try:
morphbr@516
    63
                    sub = opt.split("=")[1]
morphbr@516
    64
                    if len(sub) < 2:
morphbr@516
    65
                        self.language = sub
morphbr@516
    66
                except Exception, e:
morphbr@533
    67
                    log.error("Bad subtitle option: %s" % opt)
renatofilho@484
    68
morphbr@497
    69
            elif opt.find("format=") >= 0:
morphbr@497
    70
                try:
morphbr@516
    71
                    self.mpegopts = opt.split("=")[1]
morphbr@516
    72
                except Exception, e:
morphbr@533
    73
                    log.error("Bad format option: %s" % opt)
morphbr@497
    74
morphbr@533
    75
            elif opt.find("outfile=") >= 0:
morphbr@533
    76
                try:
morphbr@533
    77
                    self.transcode_local = opt.split("=")[1]
morphbr@533
    78
                except Exception, e:
morphbr@533
    79
                    log.error("Bad outfile option: %s" % opt)
morphbr@533
    80
morphbr@533
    81
    # setup_opts()
morphbr@533
    82
renatofilho@484
    83
renatofilho@484
    84
    def run_mplayer(self):
morphbr@514
    85
        msg = self.filename
morphbr@497
    86
morphbr@498
    87
        if self.kind == "dvd":
renatofilho@484
    88
            msg = "dvd://" + msg
renatofilho@484
    89
morphbr@514
    90
        self.mplayer_pid = Popen([self.mplayer, self.filename, "1> %s" % os.devnull,\
morphbr@514
    91
                                  "2> %s" % os.devnull], stdout=PIPE, close_fds=True)
morphbr@514
    92
morphbr@533
    93
    # run_mplayer()
morphbr@533
    94
morphbr@514
    95
morphbr@504
    96
    def setup_mencoder(self):
morphbr@499
    97
        self.path = self.config.get("Mencoder", "path")
morphbr@514
    98
        mp = Popen([self.path], stdout=PIPE, close_fds=True)
morphbr@514
    99
morphbr@514
   100
        version = mp.stdout.read().split("MEncoder ")[1].split(" (C)")[0].split("-")[-1]
morphbr@499
   101
morphbr@499
   102
        if version > "4.1.1": self.mencoder_old = False
morphbr@499
   103
        else: self.mencoder_old = True
morphbr@499
   104
morphbr@514
   105
        os.kill(mp.pid, signal.SIGKILL)
morphbr@514
   106
        log.info("Mencoder version: %s" % version)
morphbr@499
   107
morphbr@499
   108
        if self.mencoder_old:
morphbr@516
   109
            try:
morphbr@516
   110
                self.fifo = self.config.get("Mencoder", "fifo_path")
morphbr@516
   111
                os.mkfifo(self.fifo)
morphbr@516
   112
            except Exception, e:
morphbr@516
   113
                log.info("Fifo: %s" % e)
morphbr@499
   114
        else:
morphbr@499
   115
            self.fifo = "-"
renatofilho@484
   116
morphbr@533
   117
    # setup_mencoder()
morphbr@533
   118
morphbr@514
   119
morphbr@516
   120
    def setup_audio(self):
morphbr@514
   121
morphbr@516
   122
        if self.acodec == "mp3lame":
morphbr@514
   123
            return "-oac mp3lame -lameopts cbr:br=%s vol=5" % self.abitrate
morphbr@514
   124
        else:
morphbr@516
   125
            return "-oac lavc -lavcopts acodec=%s:abitrate=%s" % (\
morphbr@514
   126
                self.acodec, self.abitrate)
morphbr@514
   127
morphbr@533
   128
    # setup_audio()
morphbr@516
   129
morphbr@516
   130
morphbr@516
   131
    def setup_video(self):
morphbr@516
   132
morphbr@516
   133
        video = ""
morphbr@516
   134
morphbr@516
   135
        video += " -of %s" % self.mux
morphbr@516
   136
        video += " -ofps %s" % self.fps
morphbr@516
   137
morphbr@516
   138
        if self.vcodec == "nuv" or self.vcodec == "xvid"\
morphbr@516
   139
               or self.vcodec == "qtvideo" or self.vcodec == "copy":
morphbr@516
   140
            video += " -ovc %s" % self.vcodec
morphbr@516
   141
        else:
morphbr@516
   142
            video += " -ovc lavc -lavcopts vcodec=%s:vbitrate=%s" % (
morphbr@516
   143
                self.vcodec, self.vbitrate)
morphbr@516
   144
morphbr@516
   145
        if self.mux == "mpeg" and self.mpegopts is not None:
morphbr@516
   146
            video += " -mpegopts format=%s" % self.mpegopts
morphbr@516
   147
morphbr@516
   148
        video += " -vf scale=%s:%s" % (self.width, self.height)
morphbr@516
   149
morphbr@516
   150
        return video
morphbr@516
   151
morphbr@533
   152
    # setup_video()
morphbr@516
   153
morphbr@516
   154
morphbr@516
   155
    def arg_append(self, args, options):
morphbr@516
   156
        l = shlex.split(options)
morphbr@516
   157
        for i in l:
morphbr@516
   158
            args.append(i)
morphbr@516
   159
morphbr@533
   160
    # arg_append()
morphbr@533
   161
morphbr@516
   162
morphbr@516
   163
    def setup_args(self, args):
morphbr@516
   164
morphbr@516
   165
        args.append(self.path)
morphbr@533
   166
morphbr@533
   167
        #args.append(self.filename)
morphbr@533
   168
        args.append("-")
morphbr@516
   169
morphbr@516
   170
        if self.language != None:
morphbr@516
   171
            self.arg_append(args, "-alang %s" % self.language)
morphbr@516
   172
morphbr@516
   173
        if self.subtitle != None:
morphbr@516
   174
            self.arg_append(args, "-slang %s" % self.subtitle)
morphbr@516
   175
            self.arg_append(args, "-subfps %s" % self.fps)
morphbr@516
   176
morphbr@516
   177
        self.arg_append(args, "-idx")
morphbr@516
   178
        self.arg_append(args, self.audio_opts)
morphbr@516
   179
        self.arg_append(args, self.video_opts)
morphbr@516
   180
morphbr@516
   181
        self.arg_append(args, "-really-quiet")
morphbr@516
   182
        self.arg_append(args, "-o %s" % self.fifo)
morphbr@516
   183
        self.arg_append(args, "2> %s" % os.devnull)
morphbr@516
   184
morphbr@533
   185
    # setup_args()
morphbr@533
   186
morphbr@516
   187
morphbr@504
   188
    def setup_filename(self, filename):
morphbr@504
   189
        try:
morphbr@504
   190
            self.kind, self.filename = filename.split("://")
morphbr@504
   191
        except:
morphbr@504
   192
            return (False, "Wrong filename protocol")
morphbr@504
   193
morphbr@504
   194
        if self.kind == "file":
morphbr@504
   195
            if not os.path.exists(self.filename):
morphbr@504
   196
                msg = "File requested does not exist. SETUP failed."
morphbr@514
   197
                log.error(msg)
morphbr@504
   198
                return (False, msg)
morphbr@504
   199
morphbr@504
   200
        elif self.kind == "dvd":
morphbr@516
   201
            self.filename = "dvd://" + filename
morphbr@504
   202
morphbr@533
   203
        elif self.kind == "myth":
morphbr@533
   204
            self.filename = "myth://" + filename
morphbr@533
   205
            self.gst_pipe = os.pipe()
morphbr@533
   206
            print self.gst_pipe[0]
morphbr@533
   207
            print self.gst_pipe[1]
morphbr@533
   208
morphbr@504
   209
        return (True, "")
morphbr@504
   210
morphbr@533
   211
    # setup_filename()
morphbr@533
   212
morphbr@514
   213
morphbr@516
   214
    def setup_socket(self):
morphbr@516
   215
        if self.socket != None:
morphbr@533
   216
            self.socket = None
morphbr@514
   217
morphbr@516
   218
        self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
morphbr@516
   219
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
morphbr@514
   220
morphbr@514
   221
        try:
morphbr@516
   222
            self.socket.bind( ('', self.port) )
morphbr@516
   223
            self.socket.listen(1)
morphbr@514
   224
        except Exception, e:
morphbr@514
   225
            log.error("Could not create socket: %s" % e)
morphbr@514
   226
            return (False, e)
morphbr@514
   227
morphbr@514
   228
        return (True, "")
morphbr@514
   229
morphbr@533
   230
    # setup_socket()
morphbr@533
   231
morphbr@516
   232
morphbr@504
   233
    '''
morphbr@504
   234
    MENCODER SETUP DESCRIPTION
morphbr@504
   235
    ===========================
morphbr@504
   236
morphbr@504
   237
    -> mux, vcodecs and acodecs
morphbr@504
   238
     |-> mencoder (-of | -ovc | -oac) help
morphbr@504
   239
morphbr@504
   240
    -> if used mpeg as mux:
morphbr@504
   241
     |-> to setup format: format=%s as an option at the end
morphbr@504
   242
morphbr@504
   243
    '''
morphbr@504
   244
morphbr@516
   245
morphbr@516
   246
    # good one: /tmp/dvb.mpg avi mpeg4 400 25 mp3lame 192 320 240
morphbr@516
   247
    # file:///tmp/dvb.mpg mpeg mpeg1video 400 25 mp2 192 320 240 format=mpeg1
morphbr@516
   248
    # dvd://4 mpeg mpeg1video 400 25 mp3lame 192 400 240 language=en local
morphbr@516
   249
    # file:///tmp/mpg/bad_day.mpg avi mpeg4 400 25 mp3 192 320 240
morphbr@516
   250
morphbr@504
   251
    def setup(self, filename, mux, vcodec, vbitrate,\
morphbr@504
   252
              fps, acodec, abitrate, width, height, port, options):
morphbr@504
   253
morphbr@533
   254
        if self.args != []:
morphbr@533
   255
            self.do_cleanup()
morphbr@533
   256
morphbr@504
   257
        self.mux = mux
morphbr@504
   258
        self.vcodec = vcodec
morphbr@504
   259
        self.vbitrate = vbitrate
morphbr@504
   260
        self.fps = fps
morphbr@504
   261
        self.acodec = acodec
morphbr@504
   262
        self.abitrate = abitrate
morphbr@504
   263
        self.width = width
morphbr@504
   264
        self.height = height
morphbr@504
   265
        self.port = int(port)
morphbr@504
   266
morphbr@504
   267
        self.setup_mencoder()
morphbr@516
   268
morphbr@504
   269
        ret_val = self.setup_filename(filename)
morphbr@504
   270
        if not ret_val[0]:
morphbr@533
   271
            return ret_val
morphbr@504
   272
morphbr@516
   273
        self.setup_opts(options)
morphbr@516
   274
        self.audio_opts = self.setup_audio()
morphbr@516
   275
        self.video_opts = self.setup_video()
morphbr@516
   276
        self.setup_args(self.args)
renatofilho@484
   277
morphbr@516
   278
        ret_val = self.setup_socket()
morphbr@533
   279
        return ret_val
renatofilho@484
   280
morphbr@533
   281
    # setup()
renatofilho@484
   282
morphbr@533
   283
    def play_loop(self, conn):
morphbr@533
   284
        data = self.pout.read(4096)
renatofilho@484
   285
morphbr@533
   286
        conn.settimeout(5)
morphbr@533
   287
        retry = 0
renatofilho@484
   288
morphbr@533
   289
        if not self.transcode_local:
morphbr@498
   290
            while data != "" and retry < 5:
renatofilho@484
   291
                try:
renatofilho@484
   292
                    conn.send(data)
renatofilho@484
   293
                    r, w, x = select([conn], [], [], 0)
renatofilho@484
   294
                    if conn in r:
renatofilho@484
   295
                        back = conn.recv(1024)
morphbr@498
   296
                        if back == "OK" and self.mplayer and not self.mplayer_pid:
renatofilho@484
   297
                            self.run_mplayer()
renatofilho@484
   298
morphbr@516
   299
                except socket.error, e:
morphbr@516
   300
                    log.error("Socket error: %s" % e)
renatofilho@484
   301
                    retry += 1
renatofilho@484
   302
morphbr@516
   303
                data = self.pout.read(4096)
renatofilho@484
   304
morphbr@533
   305
        else:
morphbr@533
   306
            local = open(self.transcode_local, "w")
morphbr@533
   307
            total = os.path.getsize(self.filename)
morphbr@533
   308
            partial = 4096
morphbr@533
   309
morphbr@533
   310
            while data != "":
morphbr@533
   311
                try:
morphbr@533
   312
                    local.write(data)
morphbr@533
   313
                except Exception, e:
morphbr@533
   314
                    log.error("Write error: %s" % e)
morphbr@533
   315
morphbr@533
   316
                data = self.pout.read(4096)
morphbr@533
   317
                partial += len(data)
morphbr@533
   318
                conn.send("%.2f\n" % (partial * 100 / total) )
morphbr@533
   319
morphbr@533
   320
            local.close()
morphbr@533
   321
            conn.send("DONE\n")
morphbr@533
   322
morphbr@533
   323
        return retry
morphbr@533
   324
morphbr@533
   325
    # play_loop()
morphbr@533
   326
morphbr@533
   327
morphbr@533
   328
    def play(self):
morphbr@533
   329
morphbr@533
   330
        log.info("Starting Mencoder: %s" % self.args )
morphbr@533
   331
morphbr@533
   332
        if self.gst_pipe:
morphbr@533
   333
            try:
morphbr@533
   334
                gst = [ lib.which("gst-launch-0.10") ]
morphbr@533
   335
                self.arg_append(gst, "filesrc location=/tmp/mpg/bad_day.mpg")
morphbr@533
   336
                self.arg_append(gst, "! fdsink fd=%d" % self.gst_pipe[1])
morphbr@533
   337
                self.gst_pid = Popen(gst, stdout=self.gst_pipe[1], close_fds=True)
morphbr@533
   338
            except Exception, e:
morphbr@533
   339
                msg = "Could not init Gstreamer: %s" % e
morphbr@533
   340
                log.error(msg)
morphbr@533
   341
                return (False, msg)
morphbr@533
   342
morphbr@533
   343
        try:
morphbr@533
   344
            if not self.gst_pipe:
morphbr@533
   345
                self.stdin = open(self.filename)
morphbr@533
   346
            else:
morphbr@533
   347
                self.stdin = self.gst_pipe[0]
morphbr@533
   348
morphbr@533
   349
            self.mencoder_pid = Popen(self.args, stdin=self.stdin, stdout=PIPE, close_fds=True)
morphbr@533
   350
        except Exception, e:
morphbr@533
   351
            msg = "Could not init Mencoder: %s" % e
morphbr@533
   352
            log.error(msg)
morphbr@533
   353
            return (False, msg)
morphbr@533
   354
morphbr@533
   355
        if self.mencoder_old: self.pout = open(self.fifo)
morphbr@533
   356
        else: self.pout = self.mencoder_pid.stdout
morphbr@533
   357
morphbr@533
   358
        self.child_pid = os.fork()
morphbr@533
   359
morphbr@533
   360
        if self.child_pid == 0:
morphbr@533
   361
            conn, addr = self.socket.accept()
morphbr@533
   362
morphbr@533
   363
            log.info("Sending Data to client: %s" % addr[0])
morphbr@533
   364
            retry = self.play_loop(conn)
morphbr@533
   365
morphbr@497
   366
            if retry < 5:
morphbr@516
   367
                log.info("Finished sending Data to client: %s" % addr[0])
renatofilho@484
   368
            else:
morphbr@516
   369
                log.error("Client timed out, retried more than %s times" % retry)
renatofilho@484
   370
morphbr@516
   371
            os.kill(self.mencoder_pid.pid, signal.SIGKILL)
renatofilho@484
   372
            sys.exit(0)
renatofilho@484
   373
morphbr@533
   374
        return (True, "")
morphbr@516
   375
morphbr@533
   376
    # play()
morphbr@516
   377
renatofilho@484
   378
renatofilho@484
   379
    def stop(self):
renatofilho@484
   380
        try:
renatofilho@484
   381
morphbr@516
   382
            if self.mencoder_pid:
morphbr@533
   383
                os.kill(self.mencoder_pid.pid, signal.SIGTERM)
morphbr@516
   384
                self.mencoder_pid = None
renatofilho@484
   385
morphbr@516
   386
            if self.mplayer_pid:
morphbr@533
   387
                os.kill(self.mplayer_pid.pid, signal.SIGTERM)
morphbr@516
   388
                self.mplayer_pid = None
morphbr@516
   389
morphbr@533
   390
            if self.socket:
morphbr@516
   391
                self.socket.close()
morphbr@533
   392
                self.socket = None
morphbr@516
   393
morphbr@533
   394
            if self.child_pid:
morphbr@533
   395
                os.kill(self.child_pid, signal.SIGTERM)
morphbr@533
   396
                self.child_pid = None
morphbr@533
   397
morphbr@533
   398
            if self.gst_pid:
morphbr@533
   399
                os.kill(self.gst_pid.pid, signal.SIGTERM)
morphbr@533
   400
                self.gst_pid = None
morphbr@533
   401
morphbr@533
   402
            self.do_cleanup()
morphbr@533
   403
morphbr@533
   404
            os.wait()
morphbr@516
   405
morphbr@516
   406
        except Exception, e:
morphbr@516
   407
            log.error("Stop error: %s" % e)
morphbr@516
   408
morphbr@533
   409
    # stop()