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