diff --git a/doc/gnofract4d-manual/C/gnofract4d-manual.xml b/doc/gnofract4d-manual/C/gnofract4d-manual.xml index 9ef62a964..2f957ef11 100644 --- a/doc/gnofract4d-manual/C/gnofract4d-manual.xml +++ b/doc/gnofract4d-manual/C/gnofract4d-manual.xml @@ -345,17 +345,16 @@ keyframe will stay in video (stopped for) and interpolation type between several possibilities. When you hit Render button, Director will render all frames and put them in directory you selected and then it will try to create -video using transcode -tool. +a video using +FFmpeg. Tips: -In order to end up with video file, not just a bunch of images, you need to have -transcode tool compiled with support for ImageMagick. +In order to end up with a video file, not just a bunch of images, you need to have +ffmpeg compiled with support for zlib and libvpx. diff --git a/fract4d/animation.py b/fract4d/animation.py index 7c21721c2..bc6641315 100644 --- a/fract4d/animation.py +++ b/fract4d/animation.py @@ -1,48 +1,48 @@ #!/usr/bin/python -import os, sys, copy, math +import os +import math from xml.sax import make_parser from xml.sax.handler import ContentHandler -from . import fracttypes -from . import fractal from . import fractconfig -#interpolation type constants -INT_LINEAR= 0 -INT_LOG= 1 -INT_INVLOG= 2 -INT_COS= 3 +# interpolation type constants +INT_LINEAR = 0 +INT_LOG = 1 +INT_INVLOG = 2 +INT_COS = 3 def getAttrOrDefault(attrs, name, default): x = attrs.get(name) - if x == None: + if x is None: x = default return x def getAttrOrElse(attrs, name): x = attrs.get(name) - if x == None: + if x is None: raise ValueError( "Invalid file: Cannot find required attribute '%s'" % name) return x class KeyFrame: - def __init__(self,filename,duration,stop,int_type,flags=(0,0,0,0,0,0)): + def __init__(self, filename, duration, stop, int_type, flags=(0,0,0,0,0,0)): self.filename = filename self.duration = duration self.stop = stop self.int_type = int_type self.flags = flags - def save(self,fh): + def save(self, fh): fh.write( '\t\t\n') + @staticmethod def load_from_xml(attrs): kf = KeyFrame( getAttrOrElse(attrs,"filename"), @@ -56,8 +56,6 @@ def load_from_xml(attrs): int(getAttrOrDefault(attrs,"yw",0)), int(getAttrOrDefault(attrs,"zw",0)))) return kf - - load_from_xml=staticmethod(load_from_xml) class T: def __init__(self, compiler): @@ -65,13 +63,13 @@ def __init__(self, compiler): self.reset() def reset(self): - self.avi_file="" - self.width=640 - self.height=480 - self.framerate=25 - self.redblue=True - #keyframes is a list of KeyFrame objects - self.keyframes=[] + self.avi_file = "" + self.width = 640 + self.height = 480 + self.framerate = 25 + self.redblue = False + # keyframes is a list of KeyFrame objects + self.keyframes = [] def get_fct_enabled(self): return fractconfig.instance.getboolean("director","fct_enabled") @@ -98,63 +96,63 @@ def get_avi_file(self): return self.avi_file def set_avi_file(self,file): - if file!=None: - self.avi_file=file + if file is not None: + self.avi_file = file else: - self.avi_file="" + self.avi_file = "" def get_width(self): return self.width def set_width(self,width): - if width!=None: - self.width=int(width) + if width is not None: + self.width = int(width) else: - self.width=640 + self.width = 640 def get_height(self): return self.height def set_height(self,height): - if height!=None: - self.height=int(height) + if height is not None: + self.height = int(height) else: - self.height=480 + self.height = 480 def get_framerate(self): return self.framerate def set_framerate(self,fr): - if fr!=None: - self.framerate=int(fr) + if fr is not None: + self.framerate = int(fr) else: - self.framerate=25 + self.framerate = 25 def get_redblue(self): return self.redblue def set_redblue(self,rb): - if rb!=None: - if rb==1: - self.redblue=True - elif rb==0: - self.redblue=False - self.redblue=rb + if rb is not None: + if rb == 1: + self.redblue = True + elif rb == 0: + self.redblue = False + self.redblue = rb else: - self.redblue=True + self.redblue = True def add_keyframe(self,filename,duration,stop,int_type,index=None): kf = KeyFrame(filename,duration,stop,int_type) - if index==None: + if index is None: self.keyframes.append(kf) else: self.keyframes.insert(index, kf) def remove_keyframe(self,index): - self.keyframes[index:index+1]=[] + del self.keyframes[index:index+1] def change_keyframe(self,index,duration,stop,int_type): - if index\n') - fh.write("\n") - fh.write('\t\n') - for kf in self.keyframes: - kf.save(fh) - fh.write('\t\n') - fh.write('\t\n'% - (self.avi_file,self.framerate,self.width,self.height,self.redblue)) - fh.write("\n") - fh.close() - - #leftover from debugging purposes + with open(file, "w") as fh: + fh.write('\n') + fh.write("\n") + fh.write('\t\n') + for kf in self.keyframes: + kf.save(fh) + fh.write('\t\n') + fh.write('\t\n' % + (self.avi_file,self.framerate,self.width,self.height,self.redblue)) + fh.write("\n") + + # leftover from debugging purposes def pr(self): print(self.__dict__) def get_image_filename(self,n): "The filename of the image containing the Nth frame" - return os.path.join(self.get_png_dir(),"image_%07d.png" %n) + return os.path.join(self.get_png_dir(), "image_%07d.png" % n) def get_fractal_filename(self,n): "The filename of the .fct file which generates the Nth frame" return os.path.join(self.get_fct_dir(),"file_%07d.fct" % n) def get_mu(self, int_type, x): - if int_type==INT_LINEAR: - mu=x - elif int_type==INT_LOG: - mu=math.log(x+1,2) - elif int_type==INT_INVLOG: - mu=(math.exp(x)-1)/(math.e-1) - elif int_type==INT_COS: - mu=(1-math.cos(x*math.pi))/2 + if int_type == INT_LINEAR: + mu = x + elif int_type == INT_LOG: + mu = math.log(x+1,2) + elif int_type == INT_INVLOG: + mu = (math.exp(x)-1) / (math.e-1) + elif int_type == INT_COS: + mu = (1-math.cos(x*math.pi)) / 2 else: raise ValueError("Unknown interpolation type %d" % int_type) return mu @@ -262,18 +259,16 @@ def get_mu(self, int_type, x): # create a list containing all the filenames of the frames def create_list(self): framelist = [] - folder_png=self.get_png_dir() - - current=1 + current = 1 for i in range(self.keyframes_count()): - for j in range(self.get_keyframe_stop(i)): #output keyframe 'stop' times + for j in range(self.get_keyframe_stop(i)): # output keyframe 'stop' times framelist.append(self.get_image_filename(current-1)) if i < self.keyframes_count()-1: # final frame has no transitions following it - for j in range(self.get_keyframe_duration(i)): #output all transition files + for j in range(self.get_keyframe_duration(i)): # output all transition files framelist.append(self.get_image_filename(current)) - current=current+1 + current += 1 return framelist @@ -296,17 +291,16 @@ def get_total_frames(self): class AnimationHandler(ContentHandler): def __init__(self,animation): - self.animation=animation + self.animation = animation def startElement(self, name, attrs): - if name=="output": + if name == "output": self.animation.set_avi_file(attrs.get("filename")) self.animation.set_framerate(attrs.get("framerate")) self.animation.set_width(attrs.get("width")) self.animation.set_height(attrs.get("height")) self.animation.set_redblue(int(attrs.get("swap"))) - elif name=="keyframe": - kf= KeyFrame.load_from_xml(attrs) + elif name == "keyframe": + kf = KeyFrame.load_from_xml(attrs) self.animation.keyframes.append(kf) return - diff --git a/fract4d/fractconfig.py b/fract4d/fractconfig.py index 31b073d85..f43c35464 100644 --- a/fract4d/fractconfig.py +++ b/fract4d/fractconfig.py @@ -27,8 +27,7 @@ def __init__(self, file): "helpers" : { "editor" : self.get_default_editor(), "mailer" : self.get_default_mailer(), - "browser" : self.get_default_browser(), - "video_encoder" : "transcode" + "browser" : self.get_default_browser() }, "general" : { "threads" : "1", diff --git a/fract4d/test_animation.py b/fract4d/test_animation.py index f30a9fd6a..860ceddf2 100755 --- a/fract4d/test_animation.py +++ b/fract4d/test_animation.py @@ -29,7 +29,7 @@ def testDefault(self): self.assertEqual(self.anim.get_width(),640) self.assertEqual(self.anim.get_height(),480) self.assertEqual(self.anim.get_framerate(),25) - self.assertEqual(self.anim.get_redblue(),True) + self.assertEqual(self.anim.get_redblue(),False) self.assertEqual(self.anim.keyframes_count(),0) def testChangeOptions(self): diff --git a/fract4dgui/AVIGen.py b/fract4dgui/AVIGen.py index ca1292110..22301d5ba 100644 --- a/fract4dgui/AVIGen.py +++ b/fract4dgui/AVIGen.py @@ -1,155 +1,134 @@ -#UI and logic for generation of AVI file from bunch of images -#it knows from director bean class where images are stored (there is also a list file -#containing list of images that will be frames - needed for transcode) and it create -#thread to call transcode. +# UI and logic for generation of a video file from a bunch of images. +# Uses a list file containing the images that will be frames +# and creates a subprocess to execute ffmpeg. -#Limitations: user can destroy dialog, but it will not destroy transcode process!? - - - -from gi.repository import Gdk, Gtk, GObject, GLib import os -import re -from threading import * +import signal -from fract4d import animation, fractconfig +from gi.repository import Gdk, Gtk, GLib class AVIGeneration: - def __init__(self,animation): - self.dialog=Gtk.Dialog( - title="Generating AVI file...", + def __init__(self, animation, parent): + self.animation = animation + self.converterpath = parent.converterpath + self.fh_err = None + self.pid = None + + self.dialog = Gtk.Dialog( + transient_for=parent, + title="Generating video file", modal=True, - destroy_with_parent=True) - + destroy_with_parent=True + ) self.dialog.add_buttons(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL) - self.pbar = Gtk.ProgressBar() - self.pbar.set_text("Please wait...") - self.dialog.vbox.pack_start(self.pbar,True,True,0) + self.spinner = Gtk.Spinner() + label = Gtk.Label.new("Please wait...") + self.dialog.vbox.pack_start(self.spinner, True, True, 10) + self.dialog.vbox.pack_start(label, True, True, 10) geometry = Gdk.Geometry() geometry.min_aspect = 3.5 geometry.max_aspect = 3.5 self.dialog.set_geometry_hints(None, geometry, Gdk.WindowHints.ASPECT) - self.animation=animation - self.delete_them=-1 def generate_avi(self): - #-------getting all needed information------------------------------ - folder_png=self.animation.get_png_dir() - if folder_png[-1]!="/": - folder_png=folder_png+"/" - - avi_file=self.animation.get_avi_file() - width=self.animation.get_width() - height=self.animation.get_height() - framerate=self.animation.get_framerate() - yield True - #------------------------------------------------------------------ - - try: - if self.running==False: - yield False - return - - if not(os.path.exists(folder_png+"list")):#check if image listing already exist - Gdk.threads_enter() - error_dlg = Gtk.MessageDialog( - self.dialog, - Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, - Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, - "In directory: %s there is no listing file. Cannot continue" %(folder_png)) - response=error_dlg.run() - error_dlg.destroy() - Gdk.threads_leave() - event = Gdk.Event(Gdk.DELETE) - self.dialog.emit('delete_event', event) - yield False - return - - #--------calculating total number of frames------------ - count=self.animation.get_total_frames() - #------------------------------------------------------ - #calling transcode - swap="" - if self.animation.get_redblue(): - swap="-k" - - call="transcode -z -i %slist -x imlist,null -g %dx%d -y ffmpeg,null -F mpeg4 -f %d -o %s -H 0 --use_rgb %s 2>/dev/null"%(folder_png,width,height,framerate,avi_file,swap) - dt=DummyThread(call,self.pbar,float(count)) - dt.start() - - working=True - while(working): - dt.join(0.5) #refresh gtk every 0.5 seconds - working=dt.isAlive() - yield True - - if self.running==False: - yield False - return - yield True - except Exception as err: - self.running=False - self.error=True - Gdk.threads_enter() - error_dlg = Gtk.MessageDialog(self.dialog,Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, - Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, - _("Error during generation of avi file: %s" % err)) + folder_png = self.animation.get_png_dir() + list_file = os.path.join(folder_png, "list") + video_file = self.animation.get_avi_file() + framerate = self.animation.get_framerate() + + if not os.path.exists(list_file): + error_dlg = Gtk.MessageDialog( + transient_for=self.dialog, + title="Cannot continue", + modal=True, + destroy_with_parent=True, + message_type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.OK, + text="In directory: %s there is no listing file" % (folder_png) + ) error_dlg.run() error_dlg.destroy() - Gdk.threads_leave() - event = Gdk.Event(Gdk.DELETE) - self.dialog.emit('delete_event', event) - yield False return - if self.running==False: - yield False - return - self.running=False - self.dialog.destroy() - yield False - + + self.spinner.start() + + # calling ffmpeg + # https://trac.ffmpeg.org/wiki/Concatenate + # https://trac.ffmpeg.org/wiki/Encode/VP9 + call = [ + self.converterpath, "-nostdin", "-y", + "-loglevel", "error", "-hide_banner", + "-r", str(framerate), "-f", "concat", "-safe", "0", "-i", list_file] + if self.animation.get_redblue(): + call.extend(["-vf", "colorchannelmixer=rr=0:rb=1:br=1:bb=0"]) + call.extend([ + "-c:v", "libvpx-vp9", "-crf", "30", "-b:v", "0", + "-r", str(framerate), video_file]) + self.pid, fd_in, fd_out, fd_err = GLib.spawn_async(call, + flags=GLib.SpawnFlags.DO_NOT_REAP_CHILD, + standard_output=False, standard_error=True) + self.fh_err = os.fdopen(fd_err, "r") + GLib.child_watch_add(GLib.PRIORITY_DEFAULT, self.pid, self.video_complete) + GLib.io_add_watch(self.fh_err, GLib.PRIORITY_DEFAULT, GLib.IOCondition.IN, self.video_error) + + def video_error(self, source, condition): + error_text = source.read() + print(error_text) + error_dlg = Gtk.MessageDialog( + transient_for=self.dialog, + title=_("Error generating video file"), + modal=True, + destroy_with_parent=True, + message_type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.OK, + text=error_text) + error_dlg.run() + error_dlg.destroy() + + def video_complete(self, pid, status): + self.spinner.stop() + self.running = False + if status == 0: + self.error = False + else: + self.error = True + self.dialog.emit('delete_event', Gdk.Event()) def show(self): - #------------------------------------------------------------ self.dialog.show_all() - self.running=True - self.error=False - task=self.generate_avi() - GLib.idle_add(task.__next__) + self.running = True + self.error = False + + try: + self.generate_avi() + except GLib.GError as err: + error_dlg = Gtk.MessageDialog( + transient_for=self.dialog, + title=_("Error executing video converter"), + modal=True, + destroy_with_parent=True, + message_type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.OK, + text=str(err)) + error_dlg.run() + error_dlg.destroy() + + return -1 + + result = 0 response = self.dialog.run() - if response != Gtk.ResponseType.CANCEL: - if self.running==True: #destroy by user - self.running=False - self.dialog.destroy() - return 1 - else: - if self.error==True: #error - self.dialog.destroy() - return -1 - else: #everything ok - self.dialog.destroy() - return 0 - else: #cancel pressed - self.running=False - self.dialog.destroy() - return 1 - -#thread for calling transcode -class DummyThread(Thread): - def __init__(self,s,pbar,count): - Thread.__init__(self) - self.s=s - self.pbar=pbar - self.count=count - - def run(self): - #os.system(self.s) <----this is little faster, but user can't see any progress - reg=re.compile("\[.*-.*\]") - pipe=os.popen(self.s) - for line in pipe: - m=reg.search(line) - if m!=None: - cur=re.split("-",m.group())[1][0:-1] - self.pbar.set_fraction(float(cur)/self.count) - pipe.close() - return + if response == Gtk.ResponseType.CANCEL: + # cancel pressed + result = 1 + else: + if self.running is True: # destroy by user + GLib.spawn_close_pid(self.pid) + os.kill(self.pid, signal.SIGTERM) + result = 1 + elif self.error is True: # error + result = -1 + + if self.fh_err: + self.fh_err.close() + self.dialog.destroy() + return result diff --git a/fract4dgui/DlgAdvOpt.py b/fract4dgui/DlgAdvOpt.py index 3b227b419..083925a20 100644 --- a/fract4dgui/DlgAdvOpt.py +++ b/fract4dgui/DlgAdvOpt.py @@ -5,19 +5,20 @@ # DlgAdvOpt.py: dialog for advanced interpolation options for director # -from gi.repository import Gtk -from gi.repository import GObject -import os -import re from threading import * +from gi.repository import Gtk class DlgAdvOptions: - - def __init__(self,current_kf,animation): - self.dialog=Gtk.Dialog("Keyframe advanced options...",None, - Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, - (Gtk.STOCK_OK,Gtk.ResponseType.OK,Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL)) + def __init__(self,current_kf,animation,parent): + self.dialog=Gtk.Dialog( + transient_for=parent, + title="Keyframe advanced options", + modal=True, + destroy_with_parent=True + ) + self.dialog.add_buttons(Gtk.STOCK_OK, Gtk.ResponseType.OK, + Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) self.current_kf=current_kf self.animation=animation diff --git a/fract4dgui/FCTGen.py b/fract4dgui/FCTGen.py index a6a7e0594..193d9e047 100644 --- a/fract4dgui/FCTGen.py +++ b/fract4dgui/FCTGen.py @@ -9,29 +9,26 @@ # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA - -from gi.repository import Gtk -from gi.repository import GObject -import re import math -import sys import os -import math -from fract4d import animation, fractal, fc, fractconfig -class FCTGeneration: +from gi.repository import Gtk, GObject + +from fract4d import fractal, fc, fractconfig + +class FCTGeneration: def __init__(self,dir_bean,parent): self.dialog=Gtk.Dialog( "Generating .fct files...",parent, @@ -52,7 +49,7 @@ def generate_fct(self): self.show_error("Could not find gnofract4d version. Can not continue") yield False return - #-------------loads gnofract4d libs---------------------------- + # -------------loads gnofract4d libs---------------------------- try: self.fractal=fractal self.compiler=fc.Compiler() @@ -64,8 +61,8 @@ def generate_fct(self): self.show_error("Gnofract4d libs could not be found") yield False return - #-------------------------------------------------------------- - #find values from base keyframe first + # -------------------------------------------------------------- + # find values from base keyframe first try: ret=self.find_values(self.dir_bean.get_base_keyframe()) if len(ret)==11: @@ -79,7 +76,7 @@ def generate_fct(self): yield False return try: - #find values and duration from all keyframes + # find values and duration from all keyframes for i in range(self.dir_bean.keyframes_count()): ret=self.find_values(self.dir_bean.get_keyframe_filename(i)) if len(ret)==11: @@ -93,14 +90,14 @@ def generate_fct(self): yield False return self.durations.append(self.dir_bean.get_keyframe_duration(i)) - #interpolate and write .fct files + # interpolate and write .fct files for i in range(self.dir_bean.keyframes_count()): - if self.running==False: + if self.running is False: yield False break self.write_fct_file(i) percent=float(i+1)/float(self.dir_bean.keyframes_count()) - if self.running==False: + if self.running is False: break self.pbar.set_fraction(percent) self.pbar.set_text(str(percent*100)+"%") @@ -110,10 +107,10 @@ def generate_fct(self): yield False return - if self.running==False: + if self.running is False: yield False return - self.running=False + self.running = False self.dialog.destroy() yield False @@ -130,7 +127,7 @@ def find_version(self): pipe.close() return version - #finding x,y,z,w,size values from argument file and returns them as tuple + # finding x,y,z,w,size values from argument file and returns them as tuple def find_values(self,file): f=self.fractal.T(self.compiler) f.loadFctFile(open(file)) @@ -147,18 +144,18 @@ def find_values(self,file): zw=f.params[f.ZWANGLE] return (x,y,z,w,size,xy,xz,xw,yz,yw,zw) - #interpolate values and writes .fct files + # interpolate values and writes .fct files def write_fct_file(self,iteration): - #sum of all frames, needed for padding output files + # sum of all frames, needed for padding output files sumN=sum(self.durations) lenN=len(str(sumN)) sumBefore=sum(self.durations[0:iteration]) - #current duration + # current duration N=self.durations[iteration] - #get content of file + # get content of file f=self.fractal.T(self.compiler) f.loadFctFile(open(self.dir_bean.get_base_keyframe())) - #get all values + # get all values x1=float(self.values[iteration][f.XCENTER]) x2=float(self.values[iteration+1][f.XCENTER]) y1=float(self.values[iteration][f.YCENTER]) @@ -181,9 +178,9 @@ def write_fct_file(self,iteration): yw2=float(self.values[iteration+1][f.YWANGLE]) zw1=float(self.values[iteration][f.ZWANGLE]) zw2=float(self.values[iteration+1][f.ZWANGLE]) - #------------find direction for angles---------------------- + # ------------find direction for angles---------------------- to_right=[False]*6 - #----------xy-------------- + # ----------xy-------------- dir_xy=self.dir_bean.get_directions(iteration)[0] if dir_xy==0: if abs(xy2-xy1)xy1: + if to_right[0] is False and xy2>xy1: xy1=xy1+2*math.pi - #-------------------------- - #----------xz-------------- + # -------------------------- + # ----------xz-------------- dir_xz=self.dir_bean.get_directions(iteration)[1] if dir_xz==0: if abs(xz2-xz1)xz1: + if to_right[1] is False and xz2>xz1: xz1=xz1+2*math.pi - #-------------------------- - #----------xw-------------- + # -------------------------- + # ----------xw-------------- dir_xw=self.dir_bean.get_directions(iteration)[2] if dir_xw==0: if abs(xw2-xw1)xw1: + if to_right[2] is False and xw2>xw1: xw1=xw1+2*math.pi - #-------------------------- - #----------yz-------------- + # -------------------------- + # ----------yz-------------- dir_yz=self.dir_bean.get_directions(iteration)[3] if dir_yz==0: if abs(yz2-yz1)yz1: + if to_right[3] is False and yz2>yz1: yz1=yz1+2*math.pi - #-------------------------- - #----------yw-------------- + # -------------------------- + # ----------yw-------------- dir_yw=self.dir_bean.get_directions(iteration)[4] if dir_yw==0: if abs(yw2-yw1)yw1: + if to_right[4] is False and yw2>yw1: yw1=yw1+2*math.pi - #-------------------------- - #----------zw-------------- + # -------------------------- + # ----------zw-------------- dir_zw=self.dir_bean.get_directions(iteration)[5] if dir_zw==0: if abs(zw2-zw1)zw1: + if to_right[5] is False and zw2>zw1: zw1=zw1+2*math.pi - #-------------------------- - #------------------------------------------------------------ + # -------------------------- + # ------------------------------------------------------------ for i in range(N+1): - #depending on interpolation type, mu constant get different values from 0 to 1 + # depending on interpolation type, mu constant get different values from 0 to 1 int_type=self.dir_bean.get_keyframe_int(iteration) mu=float(i)/float(N) if int_type==INT_LINEAR: @@ -310,7 +307,7 @@ def write_fct_file(self,iteration): mu=(math.exp(mu)-1)/(math.e-1) else: mu=(1-math.cos(mu*math.pi))/2 - #calculating new values + # calculating new values new_x=x1*(1-mu)+x2*mu new_y=y1*(1-mu)+y2*mu new_z=z1*(1-mu)+z2*mu @@ -334,7 +331,7 @@ def write_fct_file(self,iteration): new_zw=zw1*(1-mu)+zw2*mu while new_zw>math.pi: new_zw=new_zw-2*math.pi - #replacing them in fractal + # replacing them in fractal f.params[f.XCENTER]=new_x f.params[f.YCENTER]=new_y f.params[f.ZCENTER]=new_z @@ -346,7 +343,7 @@ def write_fct_file(self,iteration): f.params[f.YZANGLE]=new_yz f.params[f.YWANGLE]=new_yw f.params[f.ZWANGLE]=new_zw - #writes .fct file + # writes .fct file folder=self.dir_bean.get_fct_dir() if folder[-1]!="/": folder=folder+"/" @@ -356,15 +353,15 @@ def write_fct_file(self,iteration): def show_error(self,s): self.running=False self.error=True - Gtk.threads_enter() + Gdk.threads_enter() error_dlg = Gtk.MessageDialog( self.dialog, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK,s) error_dlg.run() error_dlg.destroy() - Gtk.threads_leave() - event = Gdk.Event(Gdk.DELETE) + Gdk.threads_leave() + event = Gdk.Event(Gdk.EventType.DELETE) self.dialog.emit('delete_event', event) def show(self): @@ -375,18 +372,18 @@ def show(self): GObject.idle_add(task.__next__) response = self.dialog.run() if response != Gtk.ResponseType.CANCEL: - if self.running==True: #destroy by user + if self.running is True: # destroy by user self.running=False self.dialog.destroy() return 1 else: - if self.error==True: #error + if self.error is True: # error self.dialog.destroy() return -1 - else: #everything ok + else: # everything ok self.dialog.destroy() return 0 - else: #cancel pressed + else: # cancel pressed self.running=False self.dialog.destroy() return 1 diff --git a/fract4dgui/PNGGen.py b/fract4dgui/PNGGen.py index 7611db76d..9982f2496 100644 --- a/fract4dgui/PNGGen.py +++ b/fract4dgui/PNGGen.py @@ -1,26 +1,24 @@ -#UI and logic for generation PNG images -#It gets all information from director bean class, gets all values, and, -#in special thread, while it finds in-between values it call gtkfractal.HighResolution -#to create images +# UI and logic for generation PNG images +# It gets all information from director bean class, gets all values, and, +# in special thread, while it finds in-between values it call gtkfractal.HighResolution +# to create images -import re -import math -import sys import os from threading import * -from gi.repository import Gdk, Gtk, GObject, GLib +from gi.repository import Gdk, Gtk, GLib from . import gtkfractal, hig -from fract4d import fractal,fracttypes, animation +from fract4d import fractal running=False thread_error=False class PNGGeneration(Gtk.Dialog,hig.MessagePopper): - def __init__(self,animation,compiler): + def __init__(self, animation, compiler, parent): Gtk.Dialog.__init__( self, + transient_for=parent, title="Generating images...", modal=True, destroy_with_parent=True) @@ -40,14 +38,14 @@ def __init__(self,animation,compiler): self.set_geometry_hints(None, geometry, Gdk.WindowHints.ASPECT) self.anim=animation - #-------------loads compiler---------------------------- + # -------------loads compiler---------------------------- self.compiler=compiler def generate_png(self): global running durations=[] - #--------find values and duration from all keyframes------------ + # --------find values and duration from all keyframes------------ try: durations = self.anim.get_keyframe_durations() except Exception as err: @@ -55,7 +53,7 @@ def generate_png(self): yield False return - #--------------------------------------------------------------- + # --------------------------------------------------------------- create_all_images=self.to_create_images_again() gt=GenerationThread( durations,self.anim, @@ -68,13 +66,12 @@ def generate_png(self): working=gt.isAlive() yield True - if thread_error==True: + if thread_error is True: self.show_error("Error during image generation", "Unknown") yield False return - - if running==False: + if running is False: yield False return running=False @@ -86,7 +83,7 @@ def to_create_images_again(self): filelist = self.anim.create_list() for f in filelist: if os.path.exists(f): - Gtk.threads_enter() + Gdk.threads_enter() try: folder_png = self.anim.get_png_dir() response = self.ask_question( @@ -95,10 +92,10 @@ def to_create_images_again(self): except Exception as err: print(err) - Gtk.threads_leave() + Gdk.threads_leave() raise - Gtk.threads_leave() + Gdk.threads_leave() if response==Gtk.ResponseType.ACCEPT: create=False @@ -109,7 +106,6 @@ def to_create_images_again(self): return create def show_error(self,message,secondary): - running=False self.error=True Gdk.threads_enter() error_dlg = hig.ErrorAlert( @@ -119,7 +115,7 @@ def show_error(self,message,secondary): error_dlg.run() error_dlg.destroy() Gdk.threads_leave() - event = Gdk.Event(Gdk.DELETE) + event = Gdk.Event(Gdk.EventType.DELETE) self.emit('delete_event', event) def show(self): @@ -131,34 +127,34 @@ def show(self): GLib.idle_add(task.__next__) response = self.run() if response != Gtk.ResponseType.CANCEL: - if running==True: #destroy by user + if running is True: # destroy by user running=False self.destroy() return 1 else: - if self.error==True: #error + if self.error is True: # error self.destroy() return -1 - else: #everything ok + else: # everything ok self.destroy() return 0 - else: #cancel pressed + else: # cancel pressed running=False self.destroy() return 1 -#thread to interpolate values and calls generation of .png files +# thread to interpolate values and calls generation of .png files class GenerationThread(Thread): def __init__( - self,durations,animation,compiler, - create_all_images,pbar_image,pbar_overall): + self,durations,animation,compiler, + create_all_images,pbar_image,pbar_overall): Thread.__init__(self) self.durations=durations self.anim=animation self.create_all_images=create_all_images self.pbar_image=pbar_image self.pbar_overall=pbar_overall - #initializing progress bars + # initializing progress bars self.pbar_image.set_fraction(0) self.pbar_overall.set_fraction(0) self.pbar_overall.set_text("0/"+str(sum(self.durations)+1)) @@ -171,7 +167,7 @@ def __init__( self.current.connect('status-changed', self.onStatusChanged) self.current.connect('progress-changed', self.onProgressChanged) - #semaphore to signalize that image generation is finished + # semaphore to signalize that image generation is finished self.next_image=Semaphore(1) def onProgressChanged(self,f,progress): @@ -179,31 +175,30 @@ def onProgressChanged(self,f,progress): if running: self.pbar_image.set_fraction(progress/100.0) - #one image generation complete - tell (with "semaphore" self.next_image) we can continue + # one image generation complete - tell (with "semaphore" self.next_image) we can continue def onStatusChanged(self,f,status_val): if status_val == 0: - #release semaphore + # release semaphore self.next_image.release() def run(self): global thread_error,running import traceback try: - #first generates image from base keyframe + # first generates image from base keyframe self.generate_base_keyframe() - #pass through all keyframes and generates inter images + # pass through all keyframes and generates inter images for i in range(self.anim.keyframes_count()-1): self.generate_images(i) - if running==False: + if running is False: return - #wait for last image to finish rendering + # wait for last image to finish rendering self.next_image.acquire() - #generate list file - list = self.anim.create_list() + # generate list file lfilename = os.path.join(self.anim.get_png_dir(), "list") - lfile = open(lfilename,"w") - print("\n".join(list), file=lfile) - lfile.close() + with open(lfilename, "w") as lfile: + for imagefile in self.anim.create_list(): + print("file '%s'" % imagefile, file=lfile) except: traceback.print_exc() @@ -211,7 +206,6 @@ def run(self): running=False return - def generate_base_keyframe(self): f=fractal.T(self.compiler) fh = open(self.anim.get_keyframe_filename(0)) @@ -221,33 +215,32 @@ def generate_base_keyframe(self): fh.close() self.next_image.acquire() - #writes .fct file if user wanted that + # writes .fct file if user wanted that if self.anim.get_fct_enabled(): f.save(open(self.anim.get_fractal_filename(0),"w")) - #check if image already exist and user wants to leave it or not - if not(os.path.exists(self.anim.get_image_filename(0)) and self.create_all_images==False): #check if image already exist + # check if image already exist and user wants to leave it or not + if not(os.path.exists(self.anim.get_image_filename(0)) and self.create_all_images is False): # check if image already exist self.current.set_fractal(f) self.current.reset_render() self.current.draw_image(self.anim.get_image_filename(0)) else: - #just release semaphore + # just release semaphore self.next_image.release() return - #main method for generating images - #it generates images between iteration-th-1 and iteration-th keyframe - #first, it gets border values (keyframe values) - #(values - x,y,z,w,size,angles,formula parameters) - #then, in a loop, it generates inter values, fill fractal class with it and - #calls gtkfractal.HighResolution to generate images + # main method for generating images + # it generates images between iteration-th-1 and iteration-th keyframe + # first, it gets border values (keyframe values) + # (values - x,y,z,w,size,angles,formula parameters) + # then, in a loop, it generates inter values, fill fractal class with it and + # calls gtkfractal.HighResolution to generate images def generate_images(self,iteration): global running - #sum of all frames, needed for padding output files + # sum of all frames, needed for padding output files sumN=sum(self.durations) - lenN=len(str(sumN)) - #number of images already generated + # number of images already generated sumBefore=sum(self.durations[0:iteration]) - #current duration + # current duration N=self.durations[iteration] f_prev=fractal.T(self.compiler) @@ -264,15 +257,15 @@ def generate_images(self,iteration): finally: fh.close() - #------------------------------------------------------------ - #loop to generate images between current (iteration-th) and previous keyframe + # ------------------------------------------------------------ + # loop to generate images between current (iteration-th) and previous keyframe for i in range(1,N+1): - #but, first, wait for previous image to finish rendering + # but, first, wait for previous image to finish rendering self.next_image.acquire() - #check if user canceled us - if running==False: + # check if user canceled us + if running is False: return - #update progress bar + # update progress bar percent=float((sumBefore+i))/(sumN+1) self.pbar_overall.set_fraction(percent) self.pbar_overall.set_text(str(sumBefore+i)+"/"+str(sumN+1)) @@ -282,16 +275,16 @@ def generate_images(self,iteration): mu=self.anim.get_mu(int_type, float(i)/float(N)) f_frame = f_prev.blend(f_next,mu) - #writes .fct file if user wanted that + # writes .fct file if user wanted that if self.anim.get_fct_enabled(): f_frame.save(open(self.anim.get_fractal_filename(sumBefore+i),"w")) - #check if image already exist and user wants to leave it or not - if not(os.path.exists(self.anim.get_image_filename(sumBefore+i)) and self.create_all_images==False): #check if image already exist + # check if image already exist and user wants to leave it or not + if not(os.path.exists(self.anim.get_image_filename(sumBefore+i)) and self.create_all_images is False): # check if image already exist self.current.set_fractal(f_frame) self.current.reset_render() self.current.draw_image(self.anim.get_image_filename(sumBefore+i)) else: - #just release semaphore + # just release semaphore self.next_image.release() return diff --git a/fract4dgui/director.py b/fract4dgui/director.py index cb376be12..fe43eefd2 100644 --- a/fract4dgui/director.py +++ b/fract4dgui/director.py @@ -1,27 +1,22 @@ -#UI for gathering needed data and storing them in director bean class -#then it calls PNGGeneration, and (if everything was OK) AVIGeneration +# UI for gathering needed data and storing them in director bean class +# then it calls PNGGeneration, and (if everything was OK) AVIGeneration -#TODO: change default directory when selecting new according to already set item -#(for temp dirs, avi and fct files selections) - -from gi.repository import Gdk -from gi.repository import Gtk -from gi.repository import GObject -from gi.repository import GLib +# TODO: change default directory when selecting new according to already set item +# (for temp dirs, avi and fct files selections) import os import fnmatch -import pickle -import sys import tempfile +from gi.repository import Gdk, Gtk, GObject + from . import dialog, hig from fract4d import animation, fractconfig from . import PNGGen,AVIGen,DlgAdvOpt,director_prefs class UserCancelledError(Exception): - pass + pass class SanityCheckError(Exception): "The type of exception which is thrown when animation sanity checks fail" @@ -43,35 +38,35 @@ def check_for_keyframe_clash(self,keyframe,fct_dir): _("Keyframe %s is in the temporary .fct directory and could be overwritten. Please change temp directory." % keyframe)) def check_fct_file_sanity(self): - #things to check with fct temp dir + # things to check with fct temp dir if not self.animation.get_fct_enabled(): # we're not generating .fct files, so this is superfluous return - #check fct temp dir is set + # check fct temp dir is set if self.animation.get_fct_dir()=="": raise SanityCheckError( _("Directory for temporary .fct files not set")) - #check if it is directory + # check if it is directory if not os.path.isdir(self.animation.get_fct_dir()): raise SanityCheckError( _("Path for temporary .fct files is not a directory")) fct_path=self.animation.get_fct_dir() - #check if any keyframe fct files are in temp fct dir + # heck if any keyframe fct files are in temp fct dir for i in range(self.animation.keyframes_count()): keyframe = self.animation.get_keyframe_filename(i) self.check_for_keyframe_clash(keyframe,fct_path) - #check if there are any .fct files in temp fct dir + # check if there are any .fct files in temp fct dir has_any=False for file in os.listdir(fct_path): if fnmatch.fnmatch(file,"*.fct"): has_any=True - if has_any==True: + if has_any is True: response = self.ask_question( _("Directory for temporary .fct files contains other .fct files"), _("These may be overwritten. Proceed?")) @@ -79,36 +74,36 @@ def check_fct_file_sanity(self): raise UserCancelledError() return - #throws SanityCheckError if there was a problem + # throws SanityCheckError if there was a problem def check_sanity(self): - #check if at least two keyframes exist + # check if at least two keyframes exist if self.animation.keyframes_count()<2: raise SanityCheckError(_("There must be at least two keyframes")) - #check png temp dir is set + # check png temp dir is set if self.animation.get_png_dir()=="": raise SanityCheckError( _("Directory for temporary .png files not set")) - #check if it is directory + # check if it is directory if not os.path.isdir(self.animation.get_png_dir()): raise SanityCheckError( _("Path for temporary .png files is not a directory")) - #check avi file is set + # check avi file is set if self.animation.get_avi_file()=="": raise SanityCheckError(_("Output AVI file name not set")) self.check_fct_file_sanity() - #wrapper to show dialog for selecting .fct file - #returns selected file or empty string + # wrapper to show dialog for selecting .fct file + # returns selected file or empty string def get_fct_file(self): temp_file="" - dialog = Gtk.FileChooserDialog("Choose keyframe...",None,Gtk.FileChooserAction.OPEN, + dialog = Gtk.FileChooserDialog("Choose keyframe...",self,Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dialog.set_default_response(Gtk.ResponseType.OK) - #----setting filters--------- + # ----setting filters--------- filter = Gtk.FileFilter() filter.set_name("gnofract4d files (*.fct)") filter.add_pattern("*.fct") @@ -117,35 +112,36 @@ def get_fct_file(self): filter.set_name("All files") filter.add_pattern("*") dialog.add_filter(filter) - #---------------------------- + # ---------------------------- response = dialog.run() if response == Gtk.ResponseType.OK: temp_file=dialog.get_filename() dialog.destroy() return temp_file - #wrapper to show dialog for selecting .avi file - #returns selected file or empty string + # wrapper to show dialog for selecting .avi file + # returns selected file or empty string def get_avi_file(self): temp_file="" - dialog = Gtk.FileChooserDialog("Save AVI file...",None,Gtk.FileChooserAction.SAVE, + dialog = Gtk.FileChooserDialog("Save AVI file...",self,Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dialog.set_default_response(Gtk.ResponseType.OK) + dialog.set_filename(self.txt_temp_avi.get_text()) response = dialog.run() if response == Gtk.ResponseType.OK: temp_file=dialog.get_filename() dialog.destroy() return temp_file - #wrapper to show dialog for selecting .cfg file - #returns selected file or empty string + # wrapper to show dialog for selecting .cfg file + # returns selected file or empty string def get_cfg_file_save(self): temp_file="" - dialog = Gtk.FileChooserDialog("Save animation...",None,Gtk.FileChooserAction.SAVE, + dialog = Gtk.FileChooserDialog("Save animation...",self,Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dialog.set_default_response(Gtk.ResponseType.OK) dialog.set_current_name("animation.fcta") - #----setting filters--------- + # ----setting filters--------- filter = Gtk.FileFilter() filter.set_name("gnofract4d animation files (*.fcta)") filter.add_pattern("*.fcta") @@ -154,21 +150,21 @@ def get_cfg_file_save(self): filter.set_name("All files") filter.add_pattern("*") dialog.add_filter(filter) - #---------------------------- + # ---------------------------- response = dialog.run() if response == Gtk.ResponseType.OK: temp_file=dialog.get_filename() dialog.destroy() return temp_file - #wrapper to show dialog for selecting .fct file - #returns selected file or empty string + # wrapper to show dialog for selecting .fct file + # returns selected file or empty string def get_cfg_file_open(self): temp_file="" - dialog = Gtk.FileChooserDialog("Choose animation...",None,Gtk.FileChooserAction.OPEN, + dialog = Gtk.FileChooserDialog("Choose animation...",self,Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dialog.set_default_response(Gtk.ResponseType.OK) - #----setting filters--------- + # ----setting filters--------- filter = Gtk.FileFilter() filter.set_name("gnofract4d animation files (*.fcta)") filter.add_pattern("*.fcta") @@ -177,7 +173,7 @@ def get_cfg_file_open(self): filter.set_name("All files") filter.add_pattern("*") dialog.add_filter(filter) - #---------------------------- + # ---------------------------- response = dialog.run() if response == Gtk.ResponseType.OK: temp_file=dialog.get_filename() @@ -188,9 +184,8 @@ def get_cfg_file_open(self): def temp_avi_clicked(self,widget, data=None): avi=self.get_avi_file() - if avi!="": + if avi: self.txt_temp_avi.set_text(avi) - self.animation.set_avi_file(avi) def output_width_changed(self,widget,data=None): self.animation.set_width(self.spin_width.get_value()) @@ -219,18 +214,16 @@ def interpolation_type_changed(self,widget,data=None): self.animation.set_keyframe_int(self.current_select,int(self.cmb_interpolation_type.get_active())) self.update_model() - #point of whole program:) - #first we generate png files and list, then .avi + # point of whole program:) + # first we generate png files and list, then .avi def generate(self,create_avi=True): try: self.check_sanity() except SanityCheckError as exn: self.show_error(_("Cannot Generate Animation"), str(exn)) - return - except UserCancelledError: - return + raise - png_gen=PNGGen.PNGGeneration(self.animation, self.compiler) + png_gen=PNGGen.PNGGeneration(self.animation, self.compiler, self) res=png_gen.show() if res==1: # user cancelled, but they know that. Stop silently @@ -242,7 +235,7 @@ def generate(self,create_avi=True): if not create_avi: return - avi_gen=AVIGen.AVIGeneration(self.animation) + avi_gen=AVIGen.AVIGeneration(self.animation, self) res=avi_gen.show() if res==1: # user cancelled, but they know that. Stop silently @@ -259,23 +252,23 @@ def generate_clicked(self, widget, data=None): def adv_opt_clicked(self,widget,data=None): if self.current_select==-1: return - dlg=DlgAdvOpt.DlgAdvOptions(self.current_select,self.animation) - res=dlg.show() + dlg=DlgAdvOpt.DlgAdvOptions(self.current_select,self.animation,self) + dlg.show() - #before selecting keyframes in list box we must update values of spin boxes in case user typed something in there + # before selecting keyframes in list box we must update values of spin boxes in case user typed something in there def before_selection(self, selection, data=None, *kwargs): self.spin_duration.update() self.spin_kf_stop.update() return True - #update right box (duration, stop, interpolation type) when keyframe is selected from list + # update right box (duration, stop, interpolation type) when keyframe is selected from list def selection_changed(self,widget, data=None): (model,it)=self.tv_keyframes.get_selection().get_selected() - if it!=None: - #------getting index of selected row----------- + if it is not None: + # ------getting index of selected row----------- index=0 it=model.get_iter_first() - while it!=None: + while it is not None: if self.tv_keyframes.get_selection().iter_is_selected(it): break it=model.iter_next(it) @@ -293,11 +286,11 @@ def selection_changed(self,widget, data=None): def update_model(self): (model,it)=self.tv_keyframes.get_selection().get_selected() - if it!=None: - #------getting index of selected row----------- + if it is not None: + # ------getting index of selected row----------- index=0 it=model.get_iter_first() - while it!=None: + while it is not None: if self.tv_keyframes.get_selection().iter_is_selected(it): break it=model.iter_next(it) @@ -332,25 +325,25 @@ def add_from_current(self,widget,data=None): def add_keyframe(self,file): if file!="": - #get current seletion + # get current seletion (model,it)=self.tv_keyframes.get_selection().get_selected() - if it==None: #if it's none, just append at the end of the list + if it is None: # if it's none, just append at the end of the list it=model.append([file,25,1,"Linear"]) - else: #append after currently selected + else: # append after currently selected it=model.insert_after(it,[file,25,1,"Linear"]) - #add to bean with default parameters + # add to bean with default parameters if self.current_select!=-1: self.animation.add_keyframe(file,25,1,animation.INT_LINEAR,self.current_select+1) else: self.animation.add_keyframe(file,25,1,animation.INT_LINEAR) - #and select newly item + # and select newly item self.tv_keyframes.get_selection().select_iter(it) - #set default duration + # set default duration self.spin_duration.set_value(25) - #set default stop + # set default stop self.spin_kf_stop.set_value(1) - #set default interpolation type + # set default interpolation type self.cmb_interpolation_type.set_active(animation.INT_LINEAR) def add_keyframe_clicked(self,widget, event): @@ -363,15 +356,15 @@ def add_keyframe_clicked(self,widget, event): return False def remove_keyframe_clicked(self,widget,data=None): - #is anything selected + # is anything selected (model,it)=self.tv_keyframes.get_selection().get_selected() - if it!=None: + if it is not None: temp_curr=self.current_select model.remove(it) self.animation.remove_keyframe(temp_curr) def updateGUI(self): - #keyframes + # keyframes (model,it)=self.tv_keyframes.get_selection().get_selected() model.clear() for i in range(self.animation.keyframes_count()-1,-1,-1): @@ -388,14 +381,14 @@ def updateGUI(self): model.set(it,3,"Inverse logarithmic") else: model.set(it,3,"Cosine") - #output part + # output part self.txt_temp_avi.set_text(self.animation.get_avi_file()) self.spin_width.set_value(self.animation.get_width()) self.spin_height.set_value(self.animation.get_height()) self.spin_framerate.set_value(self.animation.get_framerate()) self.chk_swapRB.set_active(self.animation.get_redblue()) - #loads configuration file, returns 0 on ok, -1 on error (and displays error message) + # loads configuration file, returns 0 on ok, -1 on error (and displays error message) def load_configuration(self,file): if file=="": return -1 @@ -406,37 +399,37 @@ def load_configuration(self,file): _("Cannot load animation"), str(err)) return -1 - #set GUI to reflect changes + # set GUI to reflect changes self.updateGUI() return 0 - #loads configuration from pickled file + # loads configuration from pickled file def load_configuration_clicked(self,widget,data=None): cfg=self.get_cfg_file_open() if cfg!="": self.load_configuration(cfg) - #reset all field to defaults + # reset all field to defaults def new_configuration_clicked(self,widget,data=None): self.animation.reset() self.updateGUI() - #save configuration in file + # save configuration in file def save_configuration_clicked(self,widget,data=None): cfg=self.get_cfg_file_save() if cfg!="": try: - result=self.animation.save_animation(cfg) + self.animation.save_animation(cfg) except Exception as err: self.show_error( _("Error saving animation"), str(err)) def preferences_clicked(self,widget,data=None): - dlg=director_prefs.DirectorPrefs(self.animation) - res=dlg.show() + dlg=director_prefs.DirectorPrefs(self.animation, self) + dlg.show() - #creating window... + # creating window... def __init__(self, main_window, f, conf_file=""): dialog.T.__init__( self, @@ -451,14 +444,15 @@ def __init__(self, main_window, f, conf_file=""): self.f=f self.compiler = f.compiler - #main VBox - self.box_main=Gtk.VBox(False,0) - #--------------------menu------------------------------- + # main VBox + self.box_main=Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) + self.box_main.set_homogeneous(False) + # --------------------menu------------------------------- self.manager = Gtk.UIManager() accelgroup = self.manager.get_accel_group() self.add_accel_group(accelgroup) - actiongroup = Gtk.ActionGroup("Director") + actiongroup = Gtk.ActionGroup.new("Director") actiongroup.add_actions([ ('DirectorMenuAction', None, _('_Director')), ('DirectorEditAction', None, _('_Edit')), @@ -494,30 +488,27 @@ def __init__(self, main_window, f, conf_file=""): self.menubar = self.manager.get_widget('/menubar') self.box_main.pack_start(self.menubar, False, True, 0) - - #-----------creating popup menu------------------------------- - #popup menu for keyframes + # -----------creating popup menu------------------------------- + # popup menu for keyframes self.popup_menu=Gtk.Menu() - self.mnu_pop_add_file=Gtk.MenuItem("From file") + self.mnu_pop_add_file=Gtk.MenuItem.new_with_label("From file") self.popup_menu.append(self.mnu_pop_add_file) self.mnu_pop_add_file.connect("activate", self.add_from_file, None) self.mnu_pop_add_file.show() - self.mnu_pop_add_current=Gtk.MenuItem("From current fractal") + self.mnu_pop_add_current=Gtk.MenuItem.new_with_label("From current fractal") self.popup_menu.append(self.mnu_pop_add_current) self.mnu_pop_add_current.connect("activate", self.add_from_current, None) self.mnu_pop_add_current.show() - #--------------Keyframes box----------------------------------- + # --------------Keyframes box----------------------------------- self.frm_kf = Gtk.Frame.new("Keyframes") self.frm_kf.set_border_width(10) - self.hbox_kfs=Gtk.HBox(homogeneous=False,spacing=0) - self.tbl_keyframes_left=Gtk.Table(n_rows=2,n_columns=2,homogeneous=False) - self.tbl_keyframes_left.set_row_spacings(10) - self.tbl_keyframes_left.set_col_spacings(10) - self.tbl_keyframes_left.set_border_width(10) - + vbox_kfs = Gtk.Box.new(Gtk.Orientation.VERTICAL, 8) + button_box_kfs = Gtk.ButtonBox() + button_box_kfs.set_layout(Gtk.ButtonBoxStyle.SPREAD) self.sw=Gtk.ScrolledWindow() self.sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + self.sw.set_size_request(-1, 100) # filenames=Gtk.ListStore(GObject.TYPE_STRING, # GObject.TYPE_STRING) # self.tv_keyframes=Gtk.TreeView(filenames) @@ -533,7 +524,8 @@ def __init__(self, main_window, f, conf_file=""): # self.tv_column_name.add_attribute(self.cell_duration, 'text', 0) filenames=Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_UINT, GObject.TYPE_UINT, GObject.TYPE_STRING) - self.tv_keyframes = Gtk.TreeView(filenames) + self.tv_keyframes = Gtk.TreeView() + self.tv_keyframes.set_model(filenames) column = Gtk.TreeViewColumn('Keyframes', Gtk.CellRendererText(),text=0) self.tv_keyframes.append_column(column) @@ -546,23 +538,25 @@ def __init__(self, main_window, f, conf_file=""): column = Gtk.TreeViewColumn('Interpolation type', Gtk.CellRendererText(),text=3) self.tv_keyframes.append_column(column) - self.sw.add_with_viewport(self.tv_keyframes) + self.sw.add(self.tv_keyframes) self.tv_keyframes.get_selection().connect("changed",self.selection_changed,None) self.tv_keyframes.get_selection().set_select_function(self.before_selection,None) self.current_select=-1 - self.tbl_keyframes_left.attach(self.sw,0,2,0,1) self.btn_add_keyframe=Gtk.Button.new_from_stock(Gtk.STOCK_ADD) #self.btn_add_keyframe.connect("clicked",self.add_keyframe_clicked,None) self.btn_add_keyframe.connect_object("event",self.add_keyframe_clicked,self.popup_menu) - self.tbl_keyframes_left.attach(self.btn_add_keyframe,0,1,1,2,0,0) self.btn_remove_keyframe=Gtk.Button.new_from_stock(Gtk.STOCK_REMOVE) self.btn_remove_keyframe.connect("clicked",self.remove_keyframe_clicked,None) - self.tbl_keyframes_left.attach(self.btn_remove_keyframe,1,2,1,2,0,0) - self.hbox_kfs.pack_start(self.tbl_keyframes_left,True,True,10) - self.frm_kf.add(self.hbox_kfs) + button_box_kfs.pack_start(self.btn_add_keyframe, True, True, 0) + button_box_kfs.pack_start(self.btn_remove_keyframe, True, True, 0) + + vbox_kfs.pack_start(self.sw, True, True, 0) + vbox_kfs.pack_start(button_box_kfs, True, True, 10) + + self.frm_kf.add(vbox_kfs) self.box_main.pack_start(self.frm_kf,True,True,0) # current keyframe box @@ -571,31 +565,33 @@ def __init__(self, main_window, f, conf_file=""): self.box_main.pack_start(self.current_kf,True,True,0) - self.tbl_keyframes_right=Gtk.Table(n_rows=4,n_columns=2,homogeneous=True) - self.tbl_keyframes_right.set_row_spacings(10) - self.tbl_keyframes_right.set_col_spacings(10) + self.tbl_keyframes_right = Gtk.Grid() + self.tbl_keyframes_right.set_column_homogeneous(True) + self.tbl_keyframes_right.set_row_homogeneous(True) + self.tbl_keyframes_right.set_row_spacing(10) + self.tbl_keyframes_right.set_column_spacing(10) self.tbl_keyframes_right.set_border_width(10) - self.lbl_duration=Gtk.Label(label="Duration") - self.tbl_keyframes_right.attach(self.lbl_duration,0,1,0,1) + self.lbl_duration = Gtk.Label(label="Duration:") + self.tbl_keyframes_right.attach(self.lbl_duration,0,0,1,1) - adj_duration = Gtk.Adjustment(25,1,10000,1,10) + adj_duration = Gtk.Adjustment.new(25,1,10000,1,10,0) self.spin_duration = Gtk.SpinButton() self.spin_duration.set_adjustment(adj_duration) self.spin_duration.connect("output",self.duration_changed,None) - self.tbl_keyframes_right.attach(self.spin_duration,1,2,0,1) + self.tbl_keyframes_right.attach(self.spin_duration,1,0,1,1) self.lbl_kf_stop=Gtk.Label(label="Keyframe stopped for:") - self.tbl_keyframes_right.attach(self.lbl_kf_stop,0,1,1,2) + self.tbl_keyframes_right.attach(self.lbl_kf_stop,0,1,1,1) - adj_kf_stop = Gtk.Adjustment(1,1,10000,1,10) + adj_kf_stop = Gtk.Adjustment.new(1,1,10000,1,10,0) self.spin_kf_stop = Gtk.SpinButton() self.spin_duration.set_adjustment(adj_kf_stop) self.spin_kf_stop.connect("output",self.stop_changed,None) - self.tbl_keyframes_right.attach(self.spin_kf_stop,1,2,1,2) + self.tbl_keyframes_right.attach(self.spin_kf_stop,1,1,1,1) self.lbl_int_type=Gtk.Label(label="Interpolation type:") - self.tbl_keyframes_right.attach(self.lbl_int_type,0,1,2,3) + self.tbl_keyframes_right.attach(self.lbl_int_type,0,2,1,1) self.cmb_interpolation_type=Gtk.ComboBoxText() #Gtk.ComboBox() self.cmb_interpolation_type.append_text("Linear") @@ -604,26 +600,28 @@ def __init__(self, main_window, f, conf_file=""): self.cmb_interpolation_type.append_text("Cosine") self.cmb_interpolation_type.set_active(0) self.cmb_interpolation_type.connect("changed",self.interpolation_type_changed,None) - self.tbl_keyframes_right.attach(self.cmb_interpolation_type,1,2,2,3) + self.tbl_keyframes_right.attach(self.cmb_interpolation_type,1,2,1,1) self.btn_adv_opt=Gtk.Button(label="Advanced options") self.btn_adv_opt.connect("clicked",self.adv_opt_clicked,None) - self.tbl_keyframes_right.attach(self.btn_adv_opt,0,2,3,4) + self.tbl_keyframes_right.attach(self.btn_adv_opt,0,3,2,1) - self.current_kf.add(self.tbl_keyframes_right) #,False,False,10) - #------------------------------------------------------------------- - #----------------------output box----------------------------------- + self.current_kf.add(self.tbl_keyframes_right) + # ------------------------------------------------------------------- + # ----------------------output box----------------------------------- self.frm_output = Gtk.Frame.new("Output options") self.frm_output.set_border_width(10) - self.box_output_main=Gtk.VBox(homogeneous=True,spacing=10) - self.box_output_file=Gtk.HBox(homogeneous=False,spacing=10) + self.box_output_main = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, + homogeneous=True, spacing=10) + self.box_output_main.set_border_width(10) + self.box_output_file = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, + homogeneous=False, spacing=10) self.lbl_temp_avi=Gtk.Label(label="Resulting video file:") self.box_output_file.pack_start(self.lbl_temp_avi,False,False,10) self.txt_temp_avi = Gtk.Entry() - self.txt_temp_avi.set_editable(False) self.box_output_file.pack_start(self.txt_temp_avi,True,True,10) self.btn_temp_avi=Gtk.Button(label="Browse") @@ -637,19 +635,17 @@ def __init__(self, main_window, f, conf_file=""): self.lbl_res=Gtk.Label(label="Resolution:") self.box_output_res.pack_start(self.lbl_res,False,False,10) - adj_width = Gtk.Adjustment(640,320,2048,10,100,0) + adj_width = Gtk.Adjustment.new(640,320,2048,10,100,0) self.spin_width = Gtk.SpinButton() self.spin_width.set_adjustment(adj_width) - self.spin_width.connect("output",self.output_width_changed,None) self.box_output_res.pack_start(self.spin_width,False,False,10) self.lbl_x=Gtk.Label(label="x") self.box_output_res.pack_start(self.lbl_x,False,False,10) - adj_height=Gtk.Adjustment(480,240,1536,10,100,0) + adj_height=Gtk.Adjustment.new(480,240,1536,10,100,0) self.spin_height = Gtk.SpinButton() self.spin_height.set_adjustment(adj_height) - self.spin_height.connect("output",self.output_height_changed,None) self.box_output_res.pack_start(self.spin_height,False,False,10) self.box_output_main.pack_start(self.box_output_res,True,True,0) @@ -659,15 +655,12 @@ def __init__(self, main_window, f, conf_file=""): self.lbl_framerate=Gtk.Label(label="Frame rate:") self.box_output_framerate.pack_start(self.lbl_framerate,False,False,10) - adj_framerate = Gtk.Adjustment(25,5,100,1,5,0) + adj_framerate = Gtk.Adjustment.new(25,5,100,1,5,0) self.spin_framerate = Gtk.SpinButton() self.spin_framerate.set_adjustment(adj_framerate) - self.spin_framerate.connect("output",self.output_framerate_changed,None) self.box_output_framerate.pack_start(self.spin_framerate,False,False,10) self.chk_swapRB=Gtk.CheckButton(label="Swap red and blue component") - self.chk_swapRB.set_active(True) - self.chk_swapRB.connect("toggled",self.swap_redblue_clicked,None) self.box_output_framerate.pack_start(self.chk_swapRB,False,False,50) self.box_output_main.pack_start(self.box_output_framerate,True,True,0) @@ -675,10 +668,9 @@ def __init__(self, main_window, f, conf_file=""): self.frm_output.add(self.box_output_main) self.box_main.pack_start(self.frm_output,False,False,0) - - # check if transcode can be found - self.transpath = fractconfig.instance.find_on_path("transcode") - if not self.transpath: + # check if video converter can be found + self.converterpath = fractconfig.instance.find_on_path("ffmpeg") + if not self.converterpath: # put a message at the bottom to warn user warning_box = Gtk.HBox() image = Gtk.Image.new_from_stock( @@ -687,18 +679,28 @@ def __init__(self, main_window, f, conf_file=""): warning_box.pack_start(image, True, True, 0) message = Gtk.Label(label= - _("Transcode utility not found. Without it we can't generate any video but can still save sequences of still images.")) + _("ffmpeg utility not found. Without it we can't generate any video but can still save sequences of still images.")) message.set_line_wrap(True) warning_box.pack_start(message, True, True, 0) self.box_main.pack_end(warning_box, True, True, 0) - #--------------showing all------------------------------- + # initialise default settings + if conf_file: + self.load_configuration(conf_file) + else: + self.updateGUI() + + # don't connect signals until after settings initialised + self.spin_height.connect("value-changed",self.output_height_changed,None) + self.spin_width.connect("value-changed",self.output_width_changed,None) + self.spin_framerate.connect("value-changed",self.output_framerate_changed,None) + self.chk_swapRB.connect("toggled",self.swap_redblue_clicked,None) + + # --------------showing all------------------------------- self.vbox.add(self.box_main) self.vbox.show_all() self.controls = self.vbox - if conf_file!="": - self.load_configuration(conf_file) def onResponse(self,widget,id): if id == Gtk.ResponseType.CLOSE or \ @@ -706,7 +708,12 @@ def onResponse(self,widget,id): id == Gtk.ResponseType.DELETE_EVENT: self.hide() elif id == DirectorDialog.RESPONSE_RENDER: - self.generate(self.transpath != None) + self.animation.set_avi_file(self.txt_temp_avi.get_text()) + try: + self.generate(self.converterpath is not None) + except (SanityCheckError, UserCancelledError): + # prevent dialog closing if being run + GObject.signal_stop_emission_by_name(self, "response") def main(self): Gtk.main() diff --git a/fract4dgui/director_prefs.py b/fract4dgui/director_prefs.py index d840fa61b..6a0fefcc3 100644 --- a/fract4dgui/director_prefs.py +++ b/fract4dgui/director_prefs.py @@ -1,127 +1,124 @@ -from gi.repository import Gtk -from gi.repository import GObject import os -from fract4d import animation +from gi.repository import Gtk class DirectorPrefs: - - #wrapper to show dialog for selecting folder - #returns selected folder or empty string - def get_folder(self): - temp_folder="" - dialog = Gtk.FileChooserDialog("Choose directory...",None,Gtk.FileChooserAction.SELECT_FOLDER, - (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) - dialog.set_default_response(Gtk.ResponseType.OK) - response = dialog.run() - if response == Gtk.ResponseType.OK: - temp_folder=dialog.get_filename() - dialog.destroy() - return temp_folder - - def create_fct_toggled(self,widget,data=None): - self.btn_temp_fct.set_sensitive(self.chk_create_fct.get_active()) - self.txt_temp_fct.set_sensitive(self.chk_create_fct.get_active()) - - def temp_fct_clicked(self,widget, data=None): - fold=self.get_folder() - if fold!="": - self.txt_temp_fct.set_text(fold) - - def temp_png_clicked(self,widget, data=None): - fold=self.get_folder() - if fold!="": - self.txt_temp_png.set_text(fold) - - def __init__(self,animation): - self.dialog=Gtk.Dialog("Director preferences...",None, - Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, - (Gtk.STOCK_OK,Gtk.ResponseType.OK,Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL)) - - self.animation=animation - #-----------Temporary directories--------------------- - #self.frm_dirs=Gtk.Frame("Temporary directories selection") - #self.frm_dirs.set_border_width(10) - self.tbl_dirs=Gtk.Table(n_rows=3,n_columns=3,homogeneous=False) - self.tbl_dirs.set_row_spacings(10) - self.tbl_dirs.set_col_spacings(10) - self.tbl_dirs.set_border_width(10) - - self.lbl_temp_fct=Gtk.Label(label="Temporary directory for .fct files:") - self.tbl_dirs.attach(self.lbl_temp_fct,0,1,1,2) - - self.txt_temp_fct = Gtk.Entry() - self.txt_temp_fct.set_text(self.animation.get_fct_dir()) - self.txt_temp_fct.set_sensitive(False) - self.tbl_dirs.attach(self.txt_temp_fct,1,2,1,2) - - self.btn_temp_fct=Gtk.Button(label="Browse") - self.btn_temp_fct.connect("clicked",self.temp_fct_clicked,None) - self.btn_temp_fct.set_sensitive(False) - self.tbl_dirs.attach(self.btn_temp_fct,2,3,1,2) - - #this check box goes after (even if it's above above widgets because - #we connect and change its state here and it change those buttons, so they wouldn't exist - self.chk_create_fct=Gtk.CheckButton(label="Create temporary .fct files") - self.chk_create_fct.connect("toggled",self.create_fct_toggled,None) - self.chk_create_fct.set_active(self.animation.get_fct_enabled()) - self.tbl_dirs.attach(self.chk_create_fct,0,1,0,1) - - self.lbl_temp_png=Gtk.Label(label="Temporary directory for .png files:") - self.tbl_dirs.attach(self.lbl_temp_png,0,1,2,3) - - self.txt_temp_png = Gtk.Entry() - self.txt_temp_png.set_text(self.animation.get_png_dir()) - self.tbl_dirs.attach(self.txt_temp_png,1,2,2,3) - - self.btn_temp_png=Gtk.Button(label="Browse") - self.btn_temp_png.connect("clicked",self.temp_png_clicked,None) - self.tbl_dirs.attach(self.btn_temp_png,2,3,2,3) - - #self.frm_dirs.add(self.tbl_dirs) - self.dialog.vbox.pack_start(self.tbl_dirs,False,False,0) - #self.dialog.vbox.pack_start(self.tbl_main,True,True,0) - - #checking is txt fields valid dirs - def check_fields(self): - if self.chk_create_fct.get_active(): - #checking fct dir - if not os.path.isdir(self.txt_temp_fct.get_text()): - error_dlg = Gtk.MessageDialog(None,Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, - Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, - "Directory for temporary .fct files is not directory") - error_dlg.run() - error_dlg.destroy() - return False - if not os.path.isdir(self.txt_temp_png.get_text()): - error_dlg = Gtk.MessageDialog(None,Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, - Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, - "Directory for temporary .png files is not directory") - error_dlg.run() - error_dlg.destroy() - return False - return True - - def show(self): - self.dialog.show_all() - #nasty, nasty hack to keep dialog running while either it is canceled or - #valid dirs are entered - is_ok=False - write=False - while is_ok==False: - response = self.dialog.run() - if response == Gtk.ResponseType.OK: - is_ok=self.check_fields() - if is_ok: - write=True - else: - is_ok=True - - if write: - self.animation.set_fct_enabled(self.chk_create_fct.get_active()) - if self.chk_create_fct.get_active(): - self.animation.set_fct_dir(self.txt_temp_fct.get_text()) - self.animation.set_png_dir(self.txt_temp_png.get_text()) - - self.dialog.destroy() - return + # wrapper to show dialog for selecting folder + # returns selected folder or empty string + def get_folder(self): + temp_folder = "" + dialog = Gtk.FileChooserDialog("Choose directory...",None,Gtk.FileChooserAction.SELECT_FOLDER, + (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) + dialog.set_default_response(Gtk.ResponseType.OK) + response = dialog.run() + if response == Gtk.ResponseType.OK: + temp_folder = dialog.get_filename() + dialog.destroy() + return temp_folder + + def create_fct_toggled(self,widget,data=None): + self.btn_temp_fct.set_sensitive(self.chk_create_fct.get_active()) + self.txt_temp_fct.set_sensitive(self.chk_create_fct.get_active()) + + def temp_fct_clicked(self,widget, data=None): + fold = self.get_folder() + if fold != "": + self.txt_temp_fct.set_text(fold) + + def temp_png_clicked(self,widget, data=None): + fold = self.get_folder() + if fold != "": + self.txt_temp_png.set_text(fold) + + def __init__(self,animation,parent): + self.dialog = Gtk.Dialog("Director preferences...",parent, + Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, + (Gtk.STOCK_OK,Gtk.ResponseType.OK,Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL)) + + self.animation = animation + # -----------Temporary directories--------------------- + #self.frm_dirs=Gtk.Frame("Temporary directories selection") + #self.frm_dirs.set_border_width(10) + self.tbl_dirs = Gtk.Table(n_rows=3,n_columns=3,homogeneous=False) + self.tbl_dirs.set_row_spacings(10) + self.tbl_dirs.set_col_spacings(10) + self.tbl_dirs.set_border_width(10) + + self.lbl_temp_fct = Gtk.Label(label="Temporary directory for .fct files:") + self.tbl_dirs.attach(self.lbl_temp_fct,0,1,1,2) + + self.txt_temp_fct = Gtk.Entry() + self.txt_temp_fct.set_text(self.animation.get_fct_dir()) + self.txt_temp_fct.set_sensitive(False) + self.tbl_dirs.attach(self.txt_temp_fct,1,2,1,2) + + self.btn_temp_fct = Gtk.Button(label="Browse") + self.btn_temp_fct.connect("clicked",self.temp_fct_clicked,None) + self.btn_temp_fct.set_sensitive(False) + self.tbl_dirs.attach(self.btn_temp_fct,2,3,1,2) + + # this check box goes after (even if it's above above widgets because + # we connect and change its state here and it change those buttons, so they wouldn't exist + self.chk_create_fct = Gtk.CheckButton(label="Create temporary .fct files") + self.chk_create_fct.connect("toggled",self.create_fct_toggled,None) + self.chk_create_fct.set_active(self.animation.get_fct_enabled()) + self.tbl_dirs.attach(self.chk_create_fct,0,1,0,1) + + self.lbl_temp_png = Gtk.Label(label="Temporary directory for .png files:") + self.tbl_dirs.attach(self.lbl_temp_png,0,1,2,3) + + self.txt_temp_png = Gtk.Entry() + self.txt_temp_png.set_text(self.animation.get_png_dir()) + self.tbl_dirs.attach(self.txt_temp_png,1,2,2,3) + + self.btn_temp_png = Gtk.Button(label="Browse") + self.btn_temp_png.connect("clicked",self.temp_png_clicked,None) + self.tbl_dirs.attach(self.btn_temp_png,2,3,2,3) + + #self.frm_dirs.add(self.tbl_dirs) + self.dialog.vbox.pack_start(self.tbl_dirs,False,False,0) + #self.dialog.vbox.pack_start(self.tbl_main,True,True,0) + + # checking is txt fields valid dirs + def check_fields(self): + if self.chk_create_fct.get_active(): + # checking fct dir + if not os.path.isdir(self.txt_temp_fct.get_text()): + error_dlg = Gtk.MessageDialog(None,Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, + Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, + "Directory for temporary .fct files is not directory") + error_dlg.run() + error_dlg.destroy() + return False + if not os.path.isdir(self.txt_temp_png.get_text()): + error_dlg = Gtk.MessageDialog(None,Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, + Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, + "Directory for temporary .png files is not directory") + error_dlg.run() + error_dlg.destroy() + return False + return True + + def show(self): + self.dialog.show_all() + # nasty, nasty hack to keep dialog running while either it is canceled or + # valid dirs are entered + is_ok = False + write = False + while is_ok is False: + response = self.dialog.run() + if response == Gtk.ResponseType.OK: + is_ok = self.check_fields() + if is_ok: + write = True + else: + is_ok = True + + if write: + self.animation.set_fct_enabled(self.chk_create_fct.get_active()) + if self.chk_create_fct.get_active(): + self.animation.set_fct_dir(self.txt_temp_fct.get_text()) + self.animation.set_png_dir(self.txt_temp_png.get_text()) + + self.dialog.destroy() + return diff --git a/fract4dgui/test_director.py b/fract4dgui/test_director.py index 0a90606a2..3ebd6cfc6 100755 --- a/fract4dgui/test_director.py +++ b/fract4dgui/test_director.py @@ -2,10 +2,9 @@ # unit tests for renderqueue module -import unittest -import sys import os -import subprocess +import sys +import unittest import gi gi.require_version('Gtk', '3.0') @@ -17,8 +16,7 @@ if sys.path[1] != "..": sys.path.insert(1, "..") from fract4dgui import director, PNGGen, hig - -from fract4d import fractal, image, fc, animation +from fract4d import fractal, fc, animation g_comp = fc.Compiler() g_comp.add_func_path("../fract4d") @@ -62,8 +60,8 @@ def testDirectorDialog(self): self.assertEqual(True,os.path.exists("./image_0000000.png")) self.assertEqual(True,os.path.exists("./image_0000001.png")) - if dd.transpath != None: - # only check for video if transcode is installed + if dd.converterpath: + # only check for video if video converter is installed self.assertEqual(True,os.path.exists("video.avi")) dd.destroy() @@ -137,11 +135,11 @@ def testKeyframeClash(self): def testPNGGen(self): f = fractal.T(g_comp) dd= director.DirectorDialog(None,f,"") - pg = PNGGen.PNGGeneration(dd.animation,g_comp) + pg = PNGGen.PNGGeneration(dd.animation,g_comp,dd) pg.generate_png() dd.destroy() - + def suite(): return unittest.makeSuite(Test,'test')