root/indytube/tags/plumi-0.1-3/indytube.py

Revision 173, 10.5 kB (checked in by andy, 20 months ago)

inital import of indytube -engagemedia.org branch

Line 
1#!/usr/bin/python2.4
2
3import ConfigParser
4import os
5import logging
6import time
7import traceback
8import sys
9
10#3rd party libraries
11# templating system
12from Cheetah.Template import Template
13#twisted networking
14from twisted.internet import reactor
15
16class IndyTubeTranscoder(object):
17
18   def __init__(self):
19        """constructor for IndyTubeTranscoder"""
20
21   def parse_config(self,conf_file):
22        """parse config from the filename argument passed in"""
23        config = ConfigParser.RawConfigParser()
24        config.read(conf_file)
25
26        self.MENCODER_LOCATION=config.get('mencoder','MENCODER_LOCATION')
27        self.MENCODER_OPTIONS=config.get('mencoder','MENCODER_OPTIONS')
28
29        self.FFMPEG2THEORA_COMMAND=config.get('ffmpeg2theora','FFMPEG2THEORA_COMMAND')
30        self.CORTADO_LOCATION=config.get('ffmpeg2theora','CORTADO_LOCATION')
31
32        self.FLVTOOL_LOCATION=config.get('flvtool2','FLVTOOL_LOCATION')
33
34        self.BE_HOW_NICE=config.get('encoder','BE_HOW_NICE') 
35        self.CONVERT_THESE=eval(config.get('encoder','CONVERT_THESE'))
36        self.DO_ENCODING=config.getboolean('encoder','DO_ENCODING')
37        self.NUMBER_OF_PARALLEL_ENCODERS=config.getint('encoder','NUMBER_OF_PARALLEL_ENCODERS')
38        self.ENCODER_LOCKFILE_BASE=config.get('encoder','ENCODER_LOCKFILE_BASE')
39        self.POLLTIME=int(config.get('encoder','POLLTIME'))
40
41        self.VIDEO_FILE_DIRECTORY=config.get('paths','VIDEO_FILE_DIRECTORY')
42        self.FLV_FILE_DIRECTORY=config.get('paths','FLV_FILE_DIRECTORY')
43        self.INCLUDE_FILE_DIRECTORY=config.get('paths','INCLUDE_FILE_DIRECTORY')
44        self.INCLUDE_FILE_SUFFIX=config.get('paths','INCLUDE_FILE_SUFFIX')
45        self.INCLUDE_TEMPLATE=config.get('paths','INCLUDE_TEMPLATE')
46
47        self.FLOWPLAYER_LOCATION=config.get('urls','FLOWPLAYER_LOCATION')
48        self.VIDEO_SERVER_URL=config.get('urls','VIDEO_SERVER_URL')
49        self.SPLASH_IMAGE_BASE=config.get('urls','SPLASH_IMAGE_BASE')
50        self.SPLASH_IMAGE_FILE=config.get('urls','SPLASH_IMAGE_FILE')
51
52        self.LOG_FILE=config.get('logging','LOG_FILE')
53        self.LOG_LEVEL=eval(config.get('logging','LOG_LEVEL'))
54
55        logging.basicConfig(level=self.LOG_LEVEL, format='%(asctime)s %(levelname)s %(message)s', filename=self.LOG_FILE, filemode='a') 
56
57        self.ENCODER_LOCKFILE = ""
58
59   def check_lock_file(self):
60        """ inits the logging, and checks for the parallel lock files. If there are no more free spots as an encoder, returns False, else returns True"""
61        self.ENCODER_LOCKFILE=""  #this is dynamic, generated off the base
62
63        #check we arent already running (up to the number of parallel encoders) and if we are , exit
64        for n in range(0,self.NUMBER_OF_PARALLEL_ENCODERS):
65                #set the lockfile name, for later
66                self.ENCODER_LOCKFILE="%s.%s" % (self.ENCODER_LOCKFILE_BASE,n)
67                if os.path.exists(self.ENCODER_LOCKFILE):       
68                        if n==(self.NUMBER_OF_PARALLEL_ENCODERS-1):
69                                logging.info("Max encoders reached(%s), exiting." % self.NUMBER_OF_PARALLEL_ENCODERS)
70                                #we should exit the program here
71                                #sys.exit("Max encoders reached(%s), exiting." % self.NUMBER_OF_PARALLEL_ENCODERS)
72                                return False
73                else:
74                        #we have a free spot , as encoder 'n', lets make the lock file and break out
75                        os.mknod(self.ENCODER_LOCKFILE)
76                        break
77        return True
78
79   def do_transcoding_loop(self):
80        """do one transcoding loop"""
81        logging.info("Starting indytube... in %s " % self.VIDEO_FILE_DIRECTORY)
82        checked = 0
83        converted = 0
84        for root,dir,files in os.walk(self.VIDEO_FILE_DIRECTORY):
85                for f in files:
86
87                        #Start the transcoding attempt here, with file 'f'
88                        #
89                        (stem,extension)=os.path.splitext(f)
90                        if extension.lower() in self.CONVERT_THESE:  #we should convert the file
91                                checked = checked + 1
92
93                                relative_directory=root[len(self.VIDEO_FILE_DIRECTORY):]
94                                if relative_directory.startswith(os.sep):
95                                        relative_directory=relative_directory[1:]  # make sure we are relative
96
97                                videofile = os.path.join(root,f)
98                                lockfile = os.path.join(root,stem+".wetube_lock")  # we are encoding already
99                                skipfile = os.path.join(root,stem+".wetube_skip")  # we tried and failed, don't bother again
100                                flvfile  = os.path.join(self.FLV_FILE_DIRECTORY,relative_directory,stem+".flv")
101                                theorafile = os.path.join(self.FLV_FILE_DIRECTORY,relative_directory,stem+".ogg")
102                                includefile  = os.path.join(self.INCLUDE_FILE_DIRECTORY,relative_directory,stem+self.INCLUDE_FILE_SUFFIX)
103                                #logging.info("check for %s, %s, %s " % (lockfile, skipfile, flvfile))
104
105                                #check that another encoder isnt already processing this file (lockfile) or that we havent tried and failed before (skipfile)
106                                if not(os.path.exists(lockfile) or os.path.exists(skipfile)):
107                                        #OK, valid video file ready to try to transcode
108                                        logging.debug("Checking file %s, using extension %s " % ( os.path.join(root,f), extension))
109                                        try:
110                                                os.mknod(lockfile)                # touch the lock file
111
112                                                # if the flv file (autogenerated) or html snippet is not there, then reencode!
113                                                if not(os.path.exists(flvfile)) or not(os.path.exists(includefile)):
114                                                        logging.info('OK to try encoding: '+videofile)
115                                                       
116                                                        #pipe_to_null = '> /dev/null 2>&1'
117                                                        if self.DO_ENCODING: #maybe we just want to regenerate the include file!
118                                                                #mencoder flv conversion
119                                                                start_time=time.time()
120                                                                encoder_command = self.MENCODER_LOCATION + " -quiet " + videofile + " -o " + flvfile + " " + self.MENCODER_OPTIONS
121                                                                os.system('nice -n '+self.BE_HOW_NICE+' '+encoder_command)
122                                                                finish_time=time.time()
123                                                                logging.info("Encoded %s in %.2f seconds, using cmd -- %s" % (videofile,finish_time-start_time,encoder_command))
124                                                                flvtool_command = self.FLVTOOL_LOCATION+" -U stdin "+flvfile
125                                                                os.system("cat "+ flvfile +" | "+ 'nice -n '+ self.BE_HOW_NICE+' '+flvtool_command) 
126
127                                                                #ffmpeg2theora , theora/ogg conversion
128                                                                start_time=time.time() 
129                                                                theora_cmd =  self.FFMPEG2THEORA_COMMAND + ' ' + videofile + " -o " + theorafile
130                                                                os.system('nice -n '+ self.BE_HOW_NICE+' '+ theora_cmd)
131                                                                finish_time=time.time()
132                                                                logging.info("Encoded %s in %.2f seconds, using cmd -- %s" % (videofile,finish_time-start_time,theora_cmd))
133
134                                                                converted = converted + 1
135
136                                                        else:
137                                                                logging.debug("skipped encoding, will just do html template generation, if flv exists as non-zero size")
138                   
139                                                else:
140                                                        logging.debug("flv file and html file already exists, not doing transcoding")
141
142                                                #make the flash HTML snippet if the flv got created correctly.
143                                                #XXX todo, separate out the flv and ogg theora (java applet) html snippet
144                                                if os.path.exists(flvfile) and os.path.getsize(flvfile)>0:
145                                                        logging.info("Making html template - original size of %s: %.1fMB, Encoded size: %.1fMB" % (videofile,os.path.getsize(videofile)/1000000.0,os.path.getsize(flvfile)/1000000.0))
146                                                        data_map={
147                                                                        'flowplayer_location':self.FLOWPLAYER_LOCATION, 
148                                                                        'videofile':relative_directory+'/'+stem+".flv", 
149                                                                        'videobaseurl':self.VIDEO_SERVER_URL, 
150                                                                        'splashbaseurl':self.SPLASH_IMAGE_BASE, 
151                                                                        'splashfile':self.SPLASH_IMAGE_FILE, 
152                                                                        'cortado_location':self.CORTADO_LOCATION, 
153                                                                        'oggfile':stem+".ogg", 
154                                                                        'mirid':stem}
155                                                        t = Template(file=self.INCLUDE_TEMPLATE, searchList=[data_map]) 
156                                                        f=open(includefile, 'w')
157                                                        f.write(t.respond())
158                                                        f.close()
159
160                                                else:
161                                                        logging.info("FLV file size is zero - assuming encoding failed! Permanently skipping file!")
162                                                        os.mknod(skipfile)
163                       
164                                                #finished transcoding block , remove lock file
165                                                os.remove(lockfile)
166
167                                        except:
168                                                logging.info("Error while processing %s: %s" % (videofile,traceback.format_exc()))
169                                                os.remove(lockfile)
170                                                #remove this process's lockfile, this exception will stop the entire process
171                                                os.remove(self.ENCODER_LOCKFILE)
172
173                                else:
174                                        logging.debug(' lock file or skip file present for file %s ' % videofile)
175
176        #remove this process's lockfile, we have finished the loop
177        os.remove(self.ENCODER_LOCKFILE)
178        logging.info("Ending indytube... We checked %s eligble files, converted %s files " % (checked, converted))
179
180        ## end : do_transcoding_loop
181
182def main():
183
184    def looperInvoker(indytuber):
185        """recursively invoked callback, to check invoking do_transcoding_loop"""
186        logging.debug("started looperInvoker function at %s, calling loop every %s seconds " % (time.strftime("%D %H:%M:%S"), indytuber.POLLTIME))
187        #check for others
188        if indytuber.check_lock_file():
189                #do transcoding
190                indytuber.do_transcoding_loop()
191
192        #recursive, time-delayed callback
193        #periodically run this function ,to keep looping
194        # passing along the indytube object as an argument. (try not to _call_ this function or else you'll end up in infinite recursion!)
195        reactor.callLater(indytuber.POLLTIME,looperInvoker,indytuber)
196
197    #make an IndyTubeTranscoder object
198    indytuber = IndyTubeTranscoder()
199    #parse our config
200    indytuber.parse_config('indytube.conf')
201    #we have started!
202    logging.info("started main function at %s, calling loop every %s seconds " % (time.strftime("%D %H:%M:%S"), indytuber.POLLTIME))
203    #start it for real, once off
204    looperInvoker(indytuber)
205    #start the twisted reactor
206    reactor.run()
207
208# this only runs if the module was *not* imported
209if __name__ == '__main__':
210    main()
211
212# Copyright John Duda, 2006
213# Copyright Andy Nicholson, 2007
214
215# This program is free software; you can redistribute it and/or modify
216# it under the terms of the GNU General Public License as published
217# by the Free Software Foundation; either version 2 of the License,
218#  or (at your option) any later version.
219
220# This program is distributed in the hope that it will be useful,
221# but WITHOUT ANY WARRANTY; without even the implied warranty of
222# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
223# GNU General Public License for more details.
224
225# You should have received a copy of the GNU General Public License
226# along with this program; if not, write to the Free Software
227# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
228                                         
Note: See TracBrowser for help on using the browser.