#!/usr/bin/env python3 from cgitb import text import os,sys import subprocess import json from functools import wraps import socket import threading import time import signal import random import locale import gettext try: translation = gettext.translation('ffconversor',localedir='/usr/share/locale') if translation: translation.install() _ = translation.gettext except FileNotFoundError: print('No translation found!') _ = lambda x: x def einfo(func): @wraps(func) def my_inner_func(*args,**kwargs): ret = None try: ret = func(*args,**kwargs) except Exception as e: excinfo = sys.exc_info() exc = excinfo[1] tb = excinfo[2] # while not 'my_inner_func' in repr(tb.tb_frame): while tb.tb_next: tb = tb.tb_next line = tb.tb_lineno fname = tb.tb_frame.f_code.co_name errstring = exc.args[0] # filename = re.findall("file '([^']+)'",repr(tb.tb_frame))[0] filename = tb.tb_frame.f_code.co_filename print(f'[{filename}:{line}]({fname}) {errstring}') for name, value in tb.tb_frame.f_locals.items(): print(f'\t{name} = {value}') print() return ret return my_inner_func me = sys.argv[0] cwd = os.getcwd() formats_audio=['mp3','aac','vorbis','mp2','pcm_s16le','wmav2','adpcm_ms','flac','amr_nb'] formats_video=['h264','mpeg1video','flv1','mjpeg','wmv2','msmpeg4v2','vp8','h263'] formats_container=['avi','ogg','matroska','webm','mpeg','flv','mp3','mov','mp4','m4a','3gp','3g2','mj2','wav','asf','flac'] formats=formats_audio+formats_video+formats_container profiles={ 'avi': {'-c:v': 'msmpeg4v2', '-q:v': '3', '-c:a': 'adpcm_ms'}, 'mpeg': {'-c:v': 'mpeg1video','-q:v': '0','-c:a': 'mp2', '-q:a': '0'}, 'matroska': {'-c:v': 'h264', '-q:v': '0', '-crf': '25', '-preset': 'slow', '-profile:v': 'high', '-level': '4.0', '-bf': '2', '-coder': '1', '-pix_fmt': 'yuv420p', '-c:a': 'aac', '-b:a': '192k', '-ar': '48000', '-ac': '2'}, 'wmv': {'-c:v': 'wmv2', '-q:v': '0', '-c:a': 'wmav2', '-b:a': '128k'}, 'webm': {'-c:v': 'vp8', '-q:v': '0','-crf': '30', '-pix_fmt': 'yuv420p', '-c:a': 'libvorbis', '-b:a': '128k', '-ar': '48000'}, 'vp9': {'-c:v': 'libvpx-vp9', '-b:v': '2000k','-crf': '30', '-row-mt': '1', '-deadline': 'good', '-pix_fmt': 'yuv420p', '-c:a': 'libvorbis', '-b:a': '128k', '-ar': '48000','-f': 'webm'}, 'h265': {'-c:v': 'libx265', '-crf': '30','-preset': 'slow','-c:a':'aac','-b:a':'128k','-pix_fmt':'yuv420p','-ar':'48000'}, 'mp3': {'-c:a': 'mp3', '-b:a': '160k', '-ar': '44100'}, 'flac': {'-c:a': 'flac', '-ar': '48000'}, 'wav': {'-c:a': 'pcm_s16le'}, 'ogg': {'-c:a': 'libvorbis', '-b:a': '128k', '-ar': '48000'} } allowed_formats=list(profiles.keys()) profile_alias={ 'mkv': 'matroska', 'mpg': 'mpeg', 'mp4': 'matroska', 'mov': 'matroska' } codec_alias={ 'vp9': 'webm', 'h265': 'mp4' } allowed_formats.extend(profile_alias.keys()) textmode = '' kdemode = '' ffmpeg = None def help(): print(f"{os.path.basename(sys.argv[0])} -i ( | )") print('\t: -t for text mode, -k for kde mode') print('\t: input file to convert') print('\t: -o ') print('\t: -f ') print('\t: one of the following:') print('\t'+', '.join(allowed_formats)) def parse_params(): output = {} sys.argv.append('') nparams = len(sys.argv[1:]) nparam = 0 output['textmode'] = '0' output['kdemode'] = '0' while nparam < nparams: nparam += 1 param = sys.argv[nparam] if not param: continue if param == '-i': f = os.path.realpath(sys.argv[nparam+1]) if os.path.isfile(f): output.setdefault('input',f) nparam += 1 else: print('{} {}'.format(sys.argv[nparam+1],_("not exists"))) sys.exit(1) raise FileNotFoundError('{} {}'.format(sys.argv[nparam+1],_("not exists"))) continue if param == '-o': f=os.path.realpath(os.path.join(os.getcwd(),sys.argv[nparam+1])) output.setdefault('output',f) nparam += 1 continue if param == '-f': f = sys.argv[nparam+1] if f: if f in allowed_formats: output.setdefault('format',f) else: print(_('Format not valid, valid types are:')+','.join(allowed_formats)) sys.exit(1) raise TypeError(_('Format not valid, valid types are:')+','.join(allowed_formats)) else: print(_('Format not specified')) help() sys.exit(1) raise TypeError(_('Format not specified')) nparam += 1 continue if param == '-t': output['textmode'] = '1' continue if param == '-g': output['kdemode'] = '1' continue if param == '-h': help() sys.exit(0) continue print('{} {}'.format(_("Unknown parameter"),param)) help() sys.exit(1) raise TypeError('{} {}'.format(_("Unknown parameter"),param)) return output def get_info(file): if os.path.isfile(file): info = subprocess.check_output(["/usr/bin/ffprobe","-v","quiet","-print_format","json","-show_format","-show_streams",file]).decode() jinfo = json.loads(info) return jinfo else: raise FileNotFoundError('{} {}'.format(file,_("not found"))) def parse_info(fileinfo): streams = [] streams = fileinfo.get('streams',[{}]) codecs = [] types = [] container = fileinfo.get('format',{}).get('format_name') length = fileinfo.get('format',{}).get('duration') width = '0' height = '0' if length: length = str(int(float(length)*1000000)) for s in streams: codec = s.get('codec_name','') if codec: codecs.append(codec) typ = s.get('codec_type','') if typ: types.append(typ) if typ == 'video': width = s.get('width') height = s.get('height') output = {} output.setdefault('width',width) output.setdefault('height',height) output.setdefault('length',length) if len(codecs) != len(types) and container: raise TypeError(_('Fail parsing file information')) for i in range(len(codecs)): output.setdefault(i,'{}/{}'.format(types[i],codecs[i])) output.setdefault('container',container) return output def cancel_text(*args,**kwargs): global textmode textmode = '0' ffmpeg.terminate() print() udpserver = None end_server = False port = random.randint(20000,65000) def start_listener(info,output): global port if kdemode and kdemode != '0': service, path = subprocess.check_output(['kdialog','--progressbar','{} ... {}'.format(_("Converting"),os.path.basename(output)), '100', '--title', os.path.basename(sys.argv[0])]).decode().strip().split(' ') udpserver = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) try: udpserver.bind(('127.0.0.1',port)) except: port = random.randint(20000,65000) udpserver.bind(('127.0.0.1',port)) udpserver.setblocking(False) length = info.get('length') if length: length = int(length) while not end_server: msg=None try: msg, addr = udpserver.recvfrom(1024) except Exception as e: msg = None if not msg: time.sleep(1) else: if length: try: msg = msg.decode() i = msg.index('out_time_ms=') e = msg.index('\n',i) l = int(msg[i:e].split('=')[1]) msg=f'{int(l*100/length):2}' except: pass if textmode and textmode != '0': print("\r"*4,msg,'%',sep='',end='') if kdemode and kdemode != '0': try: try: subprocess.check_output(['qdbus', service, path, 'wasCancelled'],stderr=subprocess.DEVNULL) except: ffmpeg.terminate() if os.path.isfile(output): os.remove(output) return subprocess.check_call(['qdbus', service, path, 'Set', '', 'value', msg]) except: pass if textmode and textmode != '0': print('\r'*4,'100%',sep='') if kdemode and kdemode != '0': subprocess.check_call(['qdbus', service, path, 'close']) def call_ffmpeg(input,output,container,profile): global end_server, ffmpeg, port info = parse_info(get_info(input)) cmd=['/usr/bin/ffmpeg','-y','-v','quiet','-i',input] if not profile.get('-c:v'): cmd.append('-vn') if not profile.get('-c:a'): cmd.append('-an') if not profile.get('-q:v') and profile.get('-c:v'): cmd.append('-q:v') cmd.append('0') if not profile.get('-b:a'): cmd.append('-q:a') cmd.append('0') if not profile.get('-ar'): cmd.append('-ar') cmd.append('44100') if not profile.get('-ac'): cmd.append('-ac') cmd.append('2') for param,param_value in profile.items(): cmd.append(param) cmd.append(param_value) if container != 'wmv' and '-f' not in cmd: cmd.append('-f') cmd.append(container) cmd.append('-progress') cmd.append(f'udp://127.0.0.1:{port}') cmd.append(output) print(' '.join(cmd)) t = threading.Thread(target=start_listener,args=(info,output)) t.start() ffmpeg = subprocess.Popen(cmd,shell=False) ffmpeg.wait() end_server=True def ask(noformat=False,nooutput=False): global f,o,i cmd=[] formats = sorted(list(profiles.keys()) + list(profile_alias.keys())) if noformat == False: if kdemode and kdemode != '0': cmd = ['kdialog','--combobox',_('Desired format?'),'--title',os.path.basename(sys.argv[0])] cmd.extend(formats) if textmode and textmode != '0': cmd = ['dialog','--stdout','--menu',_('Desired format?'),'0','0','0'] options = zip(formats,['']*len(formats)) options = [ item for i in options for item in i ] cmd.extend(options) if cmd: try: f=subprocess.check_output(cmd,shell=False).decode().strip() except: f=None finally: if textmode and textmode != '0': subprocess.check_call(['clear'],shell=False) if not f: sys.exit(0) if nooutput == False: if textmode and textmode != '0': sizes = subprocess.check_output(['stty','size'],shell=False).decode().strip() sizes = [ int(x)-10 for x in sizes.split() ] sizes = [ int(sizes[0]*2/3), int(sizes[1]/2) ] outdir=None while not outdir or not os.path.isdir(outdir): cmd = ['dialog','--stdout','--dselect',os.path.realpath(os.path.dirname(i)),str(sizes[0]),str(sizes[1])] try: outdir=subprocess.check_output(cmd,shell=False).decode().strip() except: outdir=None finally: subprocess.check_call(['clear'],shell=False) filename=None while not filename or os.path.isfile(f'{outdir}/{filename}') or '/' in filename: # text-mode does not allow overwrite cmd = ['dialog','--stdout','--inputbox',_('Filename ?'),'0','0'] name = os.path.basename(i).split('.')[:-1] name.append(f'.{f}') name = ''.join(name) name = os.path.basename(name) cmd.append(name) try: filename=subprocess.check_output(cmd,shell=False).decode().strip() except: filename=None sys.exit(1) finally: subprocess.check_call(['clear'],shell=False) o = f'{outdir}/{filename}' if kdemode and kdemode != '0': mimes={} with open('/etc/mime.types','r') as fp: for x in fp.readlines(): fields = x.strip().split() if len(fields) < 2 or fields[0][0] == '#': continue for y in fields[1:]: mimes.setdefault(y,fields[0]) ca = codec_alias.get(f) if not ca: mimetype=mimes.get(f) else: mimetype=mimes.get(ca) filename = None name = os.path.basename(i).split('.')[:-1] name.append(f'.{f}') name = ''.join(name) name = os.path.basename(name) # Allow overwrite # while not filename or os.path.isfile(filename): cmd=['kdialog','--getsavefilename',f'{os.path.realpath(os.path.dirname(i))}/{name}',mimetype] try: filename=subprocess.check_output(cmd,shell=False).decode().strip() except Exception as e: sys.exit(0) o = filename # @einfo def main(): global textmode, kdemode global i,o,f params = parse_params() i = params.get('input') o = params.get('output') f = params.get('format') textmode = params.get('textmode') kdemode = params.get('kdemode') if textmode == '0' and kdemode == '0': textmode = '1' if textmode and textmode != '0': signal.signal(signal.SIGINT,cancel_text) if not i: print(_('Input file not specified')) help() sys.exit(1) raise FileNotFoundError(_('Input file not specified')) if not f and not o: ask() if not f and o: f = os.path.basename(o).split('.')[-1] if f not in allowed_formats: print(_('No format specified and not output file with valid extension')) help() sys.exit(1) raise TypeError(_('No format specified and not output file with valid extension')) if not o and f: ask(noformat=True) # o = os.path.basename(i).split('.')[:-1] # o.append(f'.{f}') # o = ''.join(o) # o = f'{os.path.dirname(i)}/{o}' if os.path.isdir(o) and f: newoutput = os.path.basename(i).split('.')[:-1] newoutput.append(f'.{f}') newoutput = ''.join(newoutput) o = f'{output}/{newoutput}' # info = get_info(input) # info = parse_info(info) # print(f'{input} {info}') if f in profile_alias.keys(): f = profile_alias.get(f) profile = profiles.get(f,{}) if f in codec_alias.keys(): f = codec_alias.get(f) if not profile: print(f'{_("No profile for")} {format} {_("type")}') sys.exit(1) raise TypeError(f'{_("No profile for")} {format} {_("type")}') call_ffmpeg(i,o,f,profile) return 0 if __name__ == '__main__': sys.exit(1 if main() == None else 0)