From: <te...@us...> - 2004-03-05 16:37:56
|
Update of /cvsroot/quickrip/ng In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv812 Modified Files: base.py Log Message: Completed MP3 & DiVX/XViD ripping support Index: base.py =================================================================== RCS file: /cvsroot/quickrip/ng/base.py,v retrieving revision 1.6 retrieving revision 1.7 diff -C2 -d -r1.6 -r1.7 *** base.py 5 Mar 2004 00:57:18 -0000 1.6 --- base.py 5 Mar 2004 16:15:51 -0000 1.7 *************** *** 12,16 **** from __future__ import generators from time import sleep ! import sys, os, re, popen2, ConfigParser, copy import config # QuickRip global configuration try: --- 12,16 ---- from __future__ import generators from time import sleep ! import sys, os, re, popen2, ConfigParser, copy, threading, thread import config # QuickRip global configuration try: *************** *** 44,47 **** --- 44,48 ---- """the 'actual' init function""" self.cwd = os.getcwd() + self.state = "still" self.numtitles = 0 self.titles = [] *************** *** 74,78 **** 'audiocodec': ['aencode', 'mp3'], \ 'volumead': ['aencode', '0'], \ ! 'logging': ['general', 'Off'] \ } --- 75,81 ---- 'audiocodec': ['aencode', 'mp3'], \ 'volumead': ['aencode', '0'], \ ! 'logging': ['general', 'Normal'], \ ! 'logfile': ['general', \ ! os.path.join(os.path.expanduser("~"), "quickrip_log")] \ } *************** *** 147,150 **** --- 150,188 ---- return 1 + def run(self, program, arguments, finalfunc=None, updatefunc=None, flushbuffer=0, \ + data=None, lock=None): + """Runs a program; supply program name (string) and arguments (list)""" + command = arguments + command[:0] = [self.config[program]] + if lock is None: + lock = thread.allocate_lock() + self.lock = lock + + self.thread = CommandThread(self, command, updatefunc, finalfunc, + flushbuffer, data, self.lock) + self.thread.start() + log_entry = '' + for arg in arguments: + log_entry = ''.join([log_entry, ' ', arg]) + self.log('Normal', log_entry) + + + def log(self, level, entry): + """Log an event in the UI, and possibly in a log file""" + # Send program events to UI + if level == 'Normal': + self.notify_log(entry) + # Write to log file? + if self.config['logging'] == 'Off': + return + elif self.config['logging'] == 'Normal' and level == 'Normal': + if not os.path.isfile(self.config['logfile']): + create_log = open(self.config['logfile'], 'w') + create_log.write("QuickRip log file\n\n") + create_log.close() + log = open(self.config['logfile'], 'a') + log.write(entry + "\n") + log.close() + # The following functions are threaded, but are processed in the order *************** *** 172,176 **** title['name'] = ''.join([self.raw_dvd.label, '-', str(t['trackno'])]) title['size'] = 680.0 ! title['vbr'] = float(min(2000, self.calcRate(int(t['length']), 96, 680.0))) title['abr'] = 128 title['length'] = t['length'] --- 210,214 ---- title['name'] = ''.join([self.raw_dvd.label, '-', str(t['trackno'])]) title['size'] = 680.0 ! title['vbr'] = float(min(4000, self.calcRate(int(t['length']), 96, 680.0))) title['abr'] = 128 title['length'] = t['length'] *************** *** 196,202 **** --- 234,395 ---- def ripDVD(self): + """Rip the selected titles on the DVD, spawning off + appropriate ripping methods""" self.notify_startRipping() + # Set-up QuickRip to be in the correct directory + try: + os.chdir(self.config['outputdir']) + except OSError, msg: + print "Unable to change to directory %s: %s" % \ + (self.config['outputdir'], msg) + + # Build list of titles to rip + self.torip = [] + for title in self.titles: + if title['rip'] == 'yes': + self.torip.append(title) + self.numrips = len(self.torip) + self.ripssofar = 0 + + i = 1 + for title in self.torip: + # Clean up output directory + os.popen("".join(["rm ", self.config['outputdir'], "/frameno.avi", \ + " 2>/dev/null"])) + + self.notify_newTitle(title['name'], i, self.numrips, title['vbr']) + i = i + 1 + + # Look for cropping + if not title.has_key('crop'): + sstep = int(title['length']) / 31 + if not sstep: + sstep = 1 + arguments = [''.join(["dvd://", str(title['id'])]), "-vop", \ + "cropdetect", "-nosound", "-vo", "null", "-frames", "10", \ + "-sstep", str(sstep)] + self.run('mplayer', arguments, self.cropDetect, None, 0, title) + # Wait until cropping has been found + while not title.has_key('crop'): + sleep(1) + + # Rip title + self.ripMp3Audio(title) + while self.state == 'ripping': + sleep(1) + self.ripVideo(title) + while self.state == 'ripping': + sleep(1) + #if self.config['audiocodec'] == 'ogg': + #self.ogmMerge(title) + #while self.state == 'ripping': + # sleep(1) + self.notify_finishRipping() + + + def cropDetect(self, lines, data): + re_crop = re.compile('.*-vop crop=(\d*:\d*:\d*:\d*).*') + crop_options = {} + common_crop = "" + cc_hits = 0 + title = data + for line in lines: + if re_crop.search(line): + crop = re_crop.search(line).group(1) + try: + crop_options[crop] = crop_options[crop] + 1 + if crop_options[crop] > cc_hits: + common_crop = crop + except: + crop_options[crop] = 1 + title['crop'] = common_crop + + + def ripMp3Audio(self, title): + self.notify_newPass("audio") + self.state = "ripping" + # Work out options to send to mencoder + if not self.config['volumead']: + vol = "".join([":vol=", str(self.config['volumead'])]) + else: + vol = "" + lameopts = "".join(["cbr=", str(title['abr']), str(vol)]) + arguments = ["-dvd", str(title['id']), "-alang", title['alang'], \ + "-oac", "mp3lame", "-lameopts", lameopts, "-ovc", "frameno", "-o", \ + os.path.join(self.config['outputdir'], "frameno.avi")] + # Run mencoder + self.run('mencoder', arguments, self.mencodeFinished, self.mencodeProgress, \ + flushbuffer=1, data="audio") + + + def ripVideo(self, title): + """Rip the video from a DVD title, and optionally merge with audio""" + self.notify_newPass("video") + self.state = "ripping" + + # Work out filename + output = os.path.join(self.config['outputdir'], str(title['name'])) + output = ''.join([output, ".avi"]) + ## Work out options to send to mencoder + # PDA? + if self.config['pdamode'] == 'On': + resolution = "320" + else: + resolution = "720" + # Audio? + if self.config['audiocodec'] == 'mp3': + oac = "copy" + else: + oac = "null" + # Video? + if self.config['videocodec'] == 'xvid': + ovc = "xvid" + ovc_opts_type = "-xvidencopts" + ovc_opts = "".join(["4mv:me_quality=6:mod_quant:quant_range=1-31/1-31:", \ + "bitrate=", str(int(title['vbr']))]) + else: + ovc = "lavc" + ovc_opts_type = "-lavcopts" + ovc_opts = "".join(["vcodec=mpeg4:vhq:vbitrate=", str(int(title['vbr']))]) + # Cropping + vop = "".join(["scale,crop=", title['crop']]) + # Deinterlacing? + if self.config['deinterlacing'] == 'On': + vop = "".join([vop, ",dint"]) + # Build it all up + arguments = ["-dvd", str(title['id']), "-alang", title['alang'], "-oac", oac, \ + "-ovc", ovc, ovc_opts_type, ovc_opts, "-vop", vop, "-zoom", \ + "-xy", resolution, "-o", output] + # Non-default aspect ratio? + if self.config['aspectratio'] != 'Default': + arguments.insert(4, "-aspect") + arguments.insert(5, self.config['aspectratio']) + # Subtitles? + if title['slang'] != 'None': + arguments.insert(4, "-slang") + arguments.insert(5, title['slang']) + # Go for it! + self.run('mencoder', arguments, self.mencodeFinished, self.mencodeProgress, \ + flushbuffer=1, data="video") + + + def mencodeProgress(self, line, passtype): + """Run mencoder with given arguments and report progress""" + perc = 0 + trem = 0 + re_progress = re.compile('(\d+)\%\) .*Trem:\s*(\d+\w+)\s+') + if re_progress.search(line): + perc = re_progress.search(line).group(1) + trem = re_progress.search(line).group(2) + self.notify_updateProgress(perc, trem, passtype) + + + def mencodeFinished(self, lines, passtype): + """Finish off odds and ends after rip""" + self.notify_finishTitle(passtype) + sleep(2) + self.state = "still" ## INHERIT THE CLASS AND SUBSTITUE THESE CLASS METHODS WITH YOUR *************** *** 204,207 **** --- 397,403 ---- def notify_Error(self, message): pass + + def notify_log(self, entry): + pass def notify_startScanning(self): *************** *** 231,235 **** def notify_updateProgress(self, perc, trem, tpass): pass ! def notify_finishRipping(self): pass --- 427,503 ---- def notify_updateProgress(self, perc, trem, tpass): pass ! ! def notify_finishTitle(self, passtype): ! pass ! def notify_finishRipping(self): pass + + + + class CommandThread(threading.Thread): + """Handle threading of external commands""" + def __init__(self, parent, command, updatefunc, finalfunc, flushbuffer, data, lock): + threading.Thread.__init__(self) + self.parent = parent + self.updateFunc = updatefunc + self.finalFunc = finalfunc + self.flushbuffer = flushbuffer + self.command = command + self.data = data + self.lock = lock + + def run(self): + self.lock.acquire() + + #if DEBUG: + # print self.command + + self.pipe = popen2.Popen3(self.command, 1) + pid = self.pipe.pid + totallines = [] + while 1: + try: + if self.flushbuffer: + line = self.pipe.fromchild.read(100) + else: + line = self.pipe.fromchild.readline() + + if not line: + break + else: + totallines.append(line) + if self.updateFunc: + self.updateFunc(line, self.data) + #try: + # stderr_line = os.read(self.pipe.childerr.fileno(), 100) + # self.log('Debug', stderr_line) + #except: + # pass + except: + # For PyGtk... weird! + continue + + # Clean up process table--you already handle exceptions in the function + self.kill_pipe() + + try: + os.waitpid(pid, os.WNOHANG) + except: + pass + + if self.finalFunc != None: + self.finalFunc(totallines, self.data) + + self.lock.release() + + sys.exit(2) + + + def kill_pipe(self): + """Kills current process (pipe)""" + try: + os.kill(self.pipe.pid, 9) + os.waitpid(self.pipe.pid, os.WNOHANG) + except: + pass \ No newline at end of file |