From 44623b7e15bd59873d77cf6f0e71f92d6f92549f Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 19 Feb 2018 20:13:21 +0000 Subject: [PATCH 01/14] threads_enter and threads_leave belong to Gdk --- fract4dgui/FCTGen.py | 4 ++-- fract4dgui/PNGGen.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fract4dgui/FCTGen.py b/fract4dgui/FCTGen.py index a6a7e0594..92e5fb150 100644 --- a/fract4dgui/FCTGen.py +++ b/fract4dgui/FCTGen.py @@ -356,14 +356,14 @@ 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() + Gdk.threads_leave() event = Gdk.Event(Gdk.DELETE) self.dialog.emit('delete_event', event) diff --git a/fract4dgui/PNGGen.py b/fract4dgui/PNGGen.py index 7611db76d..ff1aa4b18 100644 --- a/fract4dgui/PNGGen.py +++ b/fract4dgui/PNGGen.py @@ -86,7 +86,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 +95,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 From 71b30b040a55f7f883ddad091c69915c5c6ccc86 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 19 Feb 2018 20:13:21 +0000 Subject: [PATCH 02/14] ensure all video creation dialogs have parents --- fract4dgui/AVIGen.py | 3 ++- fract4dgui/DlgAdvOpt.py | 13 +++++++++---- fract4dgui/PNGGen.py | 3 ++- fract4dgui/director.py | 16 ++++++++-------- fract4dgui/director_prefs.py | 4 ++-- fract4dgui/test_director.py | 2 +- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/fract4dgui/AVIGen.py b/fract4dgui/AVIGen.py index ca1292110..b8cbaa136 100644 --- a/fract4dgui/AVIGen.py +++ b/fract4dgui/AVIGen.py @@ -15,8 +15,9 @@ from fract4d import animation, fractconfig class AVIGeneration: - def __init__(self,animation): + def __init__(self, animation, parent): self.dialog=Gtk.Dialog( + transient_for=parent, title="Generating AVI file...", modal=True, destroy_with_parent=True) diff --git a/fract4dgui/DlgAdvOpt.py b/fract4dgui/DlgAdvOpt.py index 3b227b419..fe0539cf6 100644 --- a/fract4dgui/DlgAdvOpt.py +++ b/fract4dgui/DlgAdvOpt.py @@ -14,10 +14,15 @@ 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/PNGGen.py b/fract4dgui/PNGGen.py index ff1aa4b18..75800362d 100644 --- a/fract4dgui/PNGGen.py +++ b/fract4dgui/PNGGen.py @@ -18,9 +18,10 @@ 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) diff --git a/fract4dgui/director.py b/fract4dgui/director.py index cb376be12..ac40a3e39 100644 --- a/fract4dgui/director.py +++ b/fract4dgui/director.py @@ -105,7 +105,7 @@ def check_sanity(self): #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--------- @@ -128,7 +128,7 @@ def get_fct_file(self): #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) response = dialog.run() @@ -141,7 +141,7 @@ def get_avi_file(self): #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") @@ -165,7 +165,7 @@ def get_cfg_file_save(self): #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--------- @@ -230,7 +230,7 @@ def generate(self,create_avi=True): except UserCancelledError: return - 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 +242,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,7 +259,7 @@ 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) + dlg=DlgAdvOpt.DlgAdvOptions(self.current_select,self.animation,self) res=dlg.show() #before selecting keyframes in list box we must update values of spin boxes in case user typed something in there @@ -433,7 +433,7 @@ def save_configuration_clicked(self,widget,data=None): str(err)) def preferences_clicked(self,widget,data=None): - dlg=director_prefs.DirectorPrefs(self.animation) + dlg=director_prefs.DirectorPrefs(self.animation, self) res=dlg.show() #creating window... diff --git a/fract4dgui/director_prefs.py b/fract4dgui/director_prefs.py index d840fa61b..cb823b172 100644 --- a/fract4dgui/director_prefs.py +++ b/fract4dgui/director_prefs.py @@ -33,8 +33,8 @@ def temp_png_clicked(self,widget, data=None): if fold!="": self.txt_temp_png.set_text(fold) - def __init__(self,animation): - self.dialog=Gtk.Dialog("Director preferences...",None, + 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)) diff --git a/fract4dgui/test_director.py b/fract4dgui/test_director.py index 0a90606a2..227337ab3 100755 --- a/fract4dgui/test_director.py +++ b/fract4dgui/test_director.py @@ -137,7 +137,7 @@ 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() From d71e8fc52eaf67b9b3c7e2a2c8c290cc4053f184 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 19 Feb 2018 20:13:21 +0000 Subject: [PATCH 03/14] create video with ffmpeg instead of transcode Options passed to transcode no longer work. transcode has not been updated for years, has been removed from Debian, and uses ffmpeg itself. --- doc/gnofract4d-manual/C/gnofract4d-manual.xml | 9 ++++----- fract4d/fractconfig.py | 3 +-- fract4dgui/AVIGen.py | 16 +++++++--------- fract4dgui/PNGGen.py | 7 +++---- fract4dgui/director.py | 8 ++++---- fract4dgui/test_director.py | 4 ++-- 6 files changed, 21 insertions(+), 26 deletions(-) diff --git a/doc/gnofract4d-manual/C/gnofract4d-manual.xml b/doc/gnofract4d-manual/C/gnofract4d-manual.xml index 9ef62a964..3c393b3d7 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. 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/fract4dgui/AVIGen.py b/fract4dgui/AVIGen.py index b8cbaa136..108652c44 100644 --- a/fract4dgui/AVIGen.py +++ b/fract4dgui/AVIGen.py @@ -36,12 +36,9 @@ def __init__(self, animation, parent): def generate_avi(self): #-------getting all needed information------------------------------ folder_png=self.animation.get_png_dir() - if folder_png[-1]!="/": - folder_png=folder_png+"/" + list_file = os.path.join(folder_png, "list") avi_file=self.animation.get_avi_file() - width=self.animation.get_width() - height=self.animation.get_height() framerate=self.animation.get_framerate() yield True #------------------------------------------------------------------ @@ -51,7 +48,7 @@ def generate_avi(self): yield False return - if not(os.path.exists(folder_png+"list")):#check if image listing already exist + if not os.path.exists(list_file): Gdk.threads_enter() error_dlg = Gtk.MessageDialog( self.dialog, @@ -61,7 +58,7 @@ def generate_avi(self): response=error_dlg.run() error_dlg.destroy() Gdk.threads_leave() - event = Gdk.Event(Gdk.DELETE) + event = Gdk.Event(Gdk.EventType.DELETE) self.dialog.emit('delete_event', event) yield False return @@ -69,12 +66,13 @@ def generate_avi(self): #--------calculating total number of frames------------ count=self.animation.get_total_frames() #------------------------------------------------------ - #calling transcode + # calling ffmpeg swap="" if self.animation.get_redblue(): - swap="-k" + swap='-vf "colorchannelmixer=rr=0:rb=1:br=1:bb=0"' - 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) + call = "ffmpeg -r %d -f concat -safe 0 -i %s %s -r %d %s" % \ + (framerate, list_file, swap, framerate, avi_file) dt=DummyThread(call,self.pbar,float(count)) dt.start() diff --git a/fract4dgui/PNGGen.py b/fract4dgui/PNGGen.py index 75800362d..b2dce4dd6 100644 --- a/fract4dgui/PNGGen.py +++ b/fract4dgui/PNGGen.py @@ -200,11 +200,10 @@ def run(self): #wait for last image to finish rendering self.next_image.acquire() #generate list file - list = self.anim.create_list() 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() diff --git a/fract4dgui/director.py b/fract4dgui/director.py index ac40a3e39..5add044fc 100644 --- a/fract4dgui/director.py +++ b/fract4dgui/director.py @@ -676,9 +676,9 @@ def __init__(self, main_window, f, conf_file=""): 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,7 +687,7 @@ 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) diff --git a/fract4dgui/test_director.py b/fract4dgui/test_director.py index 227337ab3..1eb0f7011 100755 --- a/fract4dgui/test_director.py +++ b/fract4dgui/test_director.py @@ -62,8 +62,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() From ad51a18722c61ee41c109eaae02937050cddec67 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 19 Feb 2018 20:13:21 +0000 Subject: [PATCH 04/14] allow user to enter the output video name directly into DirectorDialog If the file chooser dialog is opened start with the entered file. --- fract4dgui/director.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fract4dgui/director.py b/fract4dgui/director.py index 5add044fc..6b235ece9 100644 --- a/fract4dgui/director.py +++ b/fract4dgui/director.py @@ -131,6 +131,7 @@ def get_avi_file(self): 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() @@ -188,9 +189,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()) @@ -623,7 +623,6 @@ def __init__(self, main_window, f, conf_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") @@ -706,7 +705,8 @@ 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()) + self.generate(self.converterpath is not None) def main(self): Gtk.main() From 62d3371e87cbe56531582f1ba9ad01d9ee0cf60e Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 19 Feb 2018 20:13:21 +0000 Subject: [PATCH 05/14] provide more space for the Keyframes box in DirectorDialog Simplify layout using a GtkButtonBox. --- fract4dgui/director.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/fract4dgui/director.py b/fract4dgui/director.py index 6b235ece9..5334adf1a 100644 --- a/fract4dgui/director.py +++ b/fract4dgui/director.py @@ -510,14 +510,12 @@ def __init__(self, main_window, f, conf_file=""): #--------------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) @@ -546,23 +544,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 From bf8c9787777fb69bc068466c6f022abd080f67c5 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 19 Feb 2018 20:13:21 +0000 Subject: [PATCH 06/14] tidy video generation modules --- fract4dgui/AVIGen.py | 52 +++++++++---------- fract4dgui/FCTGen.py | 117 +++++++++++++++++++++---------------------- fract4dgui/PNGGen.py | 109 +++++++++++++++++++--------------------- 3 files changed, 132 insertions(+), 146 deletions(-) diff --git a/fract4dgui/AVIGen.py b/fract4dgui/AVIGen.py index 108652c44..e30b0177a 100644 --- a/fract4dgui/AVIGen.py +++ b/fract4dgui/AVIGen.py @@ -1,18 +1,15 @@ -#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 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. -#Limitations: user can destroy dialog, but it will not destroy transcode process!? +# 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 * -from fract4d import animation, fractconfig +from gi.repository import Gdk, Gtk, GLib class AVIGeneration: def __init__(self, animation, parent): @@ -34,17 +31,17 @@ def __init__(self, animation, parent): self.delete_them=-1 def generate_avi(self): - #-------getting all needed information------------------------------ + # -------getting all needed information------------------------------ folder_png=self.animation.get_png_dir() list_file = os.path.join(folder_png, "list") avi_file=self.animation.get_avi_file() framerate=self.animation.get_framerate() yield True - #------------------------------------------------------------------ + # ------------------------------------------------------------------ try: - if self.running==False: + if self.running is False: yield False return @@ -52,10 +49,10 @@ def generate_avi(self): Gdk.threads_enter() error_dlg = Gtk.MessageDialog( self.dialog, - Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, + 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.run() error_dlg.destroy() Gdk.threads_leave() event = Gdk.Event(Gdk.EventType.DELETE) @@ -63,9 +60,9 @@ def generate_avi(self): yield False return - #--------calculating total number of frames------------ + # --------calculating total number of frames------------ count=self.animation.get_total_frames() - #------------------------------------------------------ + # ------------------------------------------------------ # calling ffmpeg swap="" if self.animation.get_redblue(): @@ -78,11 +75,11 @@ def generate_avi(self): working=True while(working): - dt.join(0.5) #refresh gtk every 0.5 seconds + dt.join(0.5) # refresh gtk every 0.5 seconds working=dt.isAlive() yield True - if self.running==False: + if self.running is False: yield False return yield True @@ -96,20 +93,19 @@ def generate_avi(self): error_dlg.run() error_dlg.destroy() Gdk.threads_leave() - event = Gdk.Event(Gdk.DELETE) + event = Gdk.Event(Gdk.EventType.DELETE) self.dialog.emit('delete_event', event) yield False return - if self.running==False: + if self.running is False: yield False return self.running=False self.dialog.destroy() yield False - def show(self): - #------------------------------------------------------------ + # ------------------------------------------------------------ self.dialog.show_all() self.running=True self.error=False @@ -117,23 +113,23 @@ def show(self): GLib.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 -#thread for calling transcode +# thread for calling transcode class DummyThread(Thread): def __init__(self,s,pbar,count): Thread.__init__(self) @@ -147,7 +143,7 @@ def run(self): pipe=os.popen(self.s) for line in pipe: m=reg.search(line) - if m!=None: + if m is not None: cur=re.split("-",m.group())[1][0:-1] self.pbar.set_fraction(float(cur)/self.count) pipe.close() diff --git a/fract4dgui/FCTGen.py b/fract4dgui/FCTGen.py index 92e5fb150..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+"/" @@ -364,7 +361,7 @@ def show_error(self,s): error_dlg.run() error_dlg.destroy() Gdk.threads_leave() - event = Gdk.Event(Gdk.DELETE) + 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 b2dce4dd6..9982f2496 100644 --- a/fract4dgui/PNGGen.py +++ b/fract4dgui/PNGGen.py @@ -1,18 +1,15 @@ -#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 @@ -41,14 +38,14 @@ def __init__(self, animation, compiler, parent): 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: @@ -56,7 +53,7 @@ def generate_png(self): yield False return - #--------------------------------------------------------------- + # --------------------------------------------------------------- create_all_images=self.to_create_images_again() gt=GenerationThread( durations,self.anim, @@ -69,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 @@ -110,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( @@ -120,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): @@ -132,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)) @@ -172,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): @@ -180,26 +175,26 @@ 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 + # generate list file lfilename = os.path.join(self.anim.get_png_dir(), "list") with open(lfilename, "w") as lfile: for imagefile in self.anim.create_list(): @@ -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 From 2a91c4aaa3300e0028df09a1ca278489812f67d4 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 19 Feb 2018 20:13:21 +0000 Subject: [PATCH 07/14] tidy director.py and its test --- fract4dgui/director.py | 151 +++++++++++++++++------------------- fract4dgui/test_director.py | 10 +-- 2 files changed, 76 insertions(+), 85 deletions(-) diff --git a/fract4dgui/director.py b/fract4dgui/director.py index 5334adf1a..2afac8368 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...",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,15 +112,15 @@ 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...",self,Gtk.FileChooserAction.SAVE, @@ -138,15 +133,15 @@ def get_avi_file(self): 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...",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") @@ -155,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...",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") @@ -178,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() @@ -219,8 +214,8 @@ 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() @@ -228,7 +223,7 @@ def generate(self,create_avi=True): self.show_error(_("Cannot Generate Animation"), str(exn)) return except UserCancelledError: - return + return png_gen=PNGGen.PNGGeneration(self.animation, self.compiler, self) res=png_gen.show() @@ -260,22 +255,22 @@ def adv_opt_clicked(self,widget,data=None): if self.current_select==-1: return dlg=DlgAdvOpt.DlgAdvOptions(self.current_select,self.animation,self) - res=dlg.show() + 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 +288,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 +327,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 +358,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 +383,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,27 +401,27 @@ 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"), @@ -434,9 +429,9 @@ def save_configuration_clicked(self,widget,data=None): def preferences_clicked(self,widget,data=None): dlg=director_prefs.DirectorPrefs(self.animation, self) - res=dlg.show() + dlg.show() - #creating window... + # creating window... def __init__(self, main_window, f, conf_file=""): dialog.T.__init__( self, @@ -451,9 +446,9 @@ def __init__(self, main_window, f, conf_file=""): self.f=f self.compiler = f.compiler - #main VBox + # main VBox self.box_main=Gtk.VBox(False,0) - #--------------------menu------------------------------- + # --------------------menu------------------------------- self.manager = Gtk.UIManager() accelgroup = self.manager.get_accel_group() self.add_accel_group(accelgroup) @@ -494,9 +489,8 @@ 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.popup_menu.append(self.mnu_pop_add_file) @@ -507,7 +501,7 @@ def __init__(self, main_window, f, conf_file=""): 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) vbox_kfs = Gtk.Box.new(Gtk.Orientation.VERTICAL, 8) @@ -611,8 +605,8 @@ def __init__(self, main_window, f, conf_file=""): self.tbl_keyframes_right.attach(self.btn_adv_opt,0,2,3,4) self.current_kf.add(self.tbl_keyframes_right) #,False,False,10) - #------------------------------------------------------------------- - #----------------------output box----------------------------------- + # ------------------------------------------------------------------- + # ----------------------output box----------------------------------- self.frm_output = Gtk.Frame.new("Output options") self.frm_output.set_border_width(10) @@ -674,7 +668,6 @@ 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 video converter can be found self.converterpath = fractconfig.instance.find_on_path("ffmpeg") if not self.converterpath: @@ -692,7 +685,7 @@ def __init__(self, main_window, f, conf_file=""): warning_box.pack_start(message, True, True, 0) self.box_main.pack_end(warning_box, True, True, 0) - #--------------showing all------------------------------- + # --------------showing all------------------------------- self.vbox.add(self.box_main) self.vbox.show_all() self.controls = self.vbox diff --git a/fract4dgui/test_director.py b/fract4dgui/test_director.py index 1eb0f7011..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") @@ -141,7 +139,7 @@ def testPNGGen(self): pg.generate_png() dd.destroy() - + def suite(): return unittest.makeSuite(Test,'test') From 9ead68d63355f1b327908c2c8d6dfcd6c51eefb2 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 19 Feb 2018 20:13:21 +0000 Subject: [PATCH 08/14] tidy video generation options dialogs --- fract4dgui/DlgAdvOpt.py | 6 +- fract4dgui/director_prefs.py | 243 +++++++++++++++++------------------ 2 files changed, 121 insertions(+), 128 deletions(-) diff --git a/fract4dgui/DlgAdvOpt.py b/fract4dgui/DlgAdvOpt.py index fe0539cf6..083925a20 100644 --- a/fract4dgui/DlgAdvOpt.py +++ b/fract4dgui/DlgAdvOpt.py @@ -5,15 +5,11 @@ # 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,parent): self.dialog=Gtk.Dialog( transient_for=parent, diff --git a/fract4dgui/director_prefs.py b/fract4dgui/director_prefs.py index cb823b172..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,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==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 From 46a1ada66b35b03be1814c7f08f9b0fcc414fb36 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Wed, 21 Feb 2018 19:55:37 +0000 Subject: [PATCH 09/14] tidy animation.py --- fract4d/animation.py | 170 +++++++++++++++++++++---------------------- 1 file changed, 82 insertions(+), 88 deletions(-) diff --git a/fract4d/animation.py b/fract4d/animation.py index 7c21721c2..d0efb488f 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 = True + # 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 - From bb02ce61a151552d747530dda8186eb3a6643df8 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Wed, 21 Feb 2018 19:55:37 +0000 Subject: [PATCH 10/14] set default for swap red and blue to off for video --- fract4d/animation.py | 2 +- fract4d/test_animation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fract4d/animation.py b/fract4d/animation.py index d0efb488f..bc6641315 100644 --- a/fract4d/animation.py +++ b/fract4d/animation.py @@ -67,7 +67,7 @@ def reset(self): self.width = 640 self.height = 480 self.framerate = 25 - self.redblue = True + self.redblue = False # keyframes is a list of KeyFrame objects self.keyframes = [] 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): From df891a71bae6b80b2d4ae7e38027706441e1fe0f Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Wed, 21 Feb 2018 19:55:37 +0000 Subject: [PATCH 11/14] fix deprecation warnings in director.py --- fract4dgui/director.py | 55 ++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/fract4dgui/director.py b/fract4dgui/director.py index 2afac8368..77f0c06fb 100644 --- a/fract4dgui/director.py +++ b/fract4dgui/director.py @@ -447,13 +447,14 @@ def __init__(self, main_window, f, conf_file=""): self.compiler = f.compiler # main VBox - self.box_main=Gtk.VBox(False,0) + 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')), @@ -492,11 +493,11 @@ def __init__(self, main_window, f, conf_file=""): # -----------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() @@ -525,7 +526,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) @@ -565,31 +567,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") @@ -598,20 +602,23 @@ 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) + 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) @@ -630,7 +637,7 @@ 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) @@ -639,7 +646,7 @@ def __init__(self, main_window, f, conf_file=""): 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) @@ -652,7 +659,7 @@ 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) From 9ecfc40fd91763d634ee239e2374285d98993c1b Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Wed, 21 Feb 2018 19:55:37 +0000 Subject: [PATCH 12/14] fix initialisation of DirectorDialog settings All values were being set to zero. --- fract4dgui/director.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/fract4dgui/director.py b/fract4dgui/director.py index 77f0c06fb..26161ee7b 100644 --- a/fract4dgui/director.py +++ b/fract4dgui/director.py @@ -640,7 +640,6 @@ def __init__(self, main_window, f, conf_file=""): 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") @@ -649,7 +648,6 @@ def __init__(self, main_window, f, conf_file=""): 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) @@ -662,12 +660,9 @@ def __init__(self, main_window, f, conf_file=""): 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) @@ -692,12 +687,22 @@ def __init__(self, main_window, f, conf_file=""): warning_box.pack_start(message, True, True, 0) self.box_main.pack_end(warning_box, True, True, 0) + # 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 \ From 1cfb0009655ff724f1778ef6da1689f4da31076c Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Wed, 21 Feb 2018 19:55:37 +0000 Subject: [PATCH 13/14] spawn ffmpeg as a process using GLib instead of threading Specifying the video codec seems necesary to produce a quality output. Use open WebM format with VP9 encoding Also: - captures output status from ffmpeg - kills ffmpeg when user quits --- doc/gnofract4d-manual/C/gnofract4d-manual.xml | 2 +- fract4dgui/AVIGen.py | 230 ++++++++---------- 2 files changed, 108 insertions(+), 124 deletions(-) diff --git a/doc/gnofract4d-manual/C/gnofract4d-manual.xml b/doc/gnofract4d-manual/C/gnofract4d-manual.xml index 3c393b3d7..2f957ef11 100644 --- a/doc/gnofract4d-manual/C/gnofract4d-manual.xml +++ b/doc/gnofract4d-manual/C/gnofract4d-manual.xml @@ -354,7 +354,7 @@ Tips: 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. +ffmpeg compiled with support for zlib and libvpx. diff --git a/fract4dgui/AVIGen.py b/fract4dgui/AVIGen.py index e30b0177a..22301d5ba 100644 --- a/fract4dgui/AVIGen.py +++ b/fract4dgui/AVIGen.py @@ -1,150 +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. - -# Limitations: user can destroy dialog, but it will not destroy transcode process!? +# 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. import os -import re -from threading import * +import signal from gi.repository import Gdk, Gtk, GLib class AVIGeneration: def __init__(self, animation, parent): - self.dialog=Gtk.Dialog( + self.animation = animation + self.converterpath = parent.converterpath + self.fh_err = None + self.pid = None + + self.dialog = Gtk.Dialog( transient_for=parent, - title="Generating AVI file...", + 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() + 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() - avi_file=self.animation.get_avi_file() - framerate=self.animation.get_framerate() - yield True - # ------------------------------------------------------------------ - - try: - if self.running is False: - yield False - return - - if not os.path.exists(list_file): - 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)) - error_dlg.run() - error_dlg.destroy() - Gdk.threads_leave() - event = Gdk.Event(Gdk.EventType.DELETE) - self.dialog.emit('delete_event', event) - yield False - return - - # --------calculating total number of frames------------ - count=self.animation.get_total_frames() - # ------------------------------------------------------ - # calling ffmpeg - swap="" - if self.animation.get_redblue(): - swap='-vf "colorchannelmixer=rr=0:rb=1:br=1:bb=0"' - - call = "ffmpeg -r %d -f concat -safe 0 -i %s %s -r %d %s" % \ - (framerate, list_file, swap, framerate, avi_file) - 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 is 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)) + 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.EventType.DELETE) - self.dialog.emit('delete_event', event) - yield False - return - if self.running is 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 response == Gtk.ResponseType.CANCEL: + # cancel pressed + result = 1 + else: if self.running is True: # destroy by user - self.running=False - self.dialog.destroy() - return 1 - else: - if self.error is 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 + GLib.spawn_close_pid(self.pid) + os.kill(self.pid, signal.SIGTERM) + result = 1 + elif self.error is True: # error + result = -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 is not None: - cur=re.split("-",m.group())[1][0:-1] - self.pbar.set_fraction(float(cur)/self.count) - pipe.close() - return + if self.fh_err: + self.fh_err.close() + self.dialog.destroy() + return result From 53585d6a60ddd01fe1f685992bdd58857c91959b Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Wed, 21 Feb 2018 19:55:37 +0000 Subject: [PATCH 14/14] don't close DirectorDialog if sanity check fails Avoid user having to enter all the details again. --- fract4dgui/director.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fract4dgui/director.py b/fract4dgui/director.py index 26161ee7b..fe43eefd2 100644 --- a/fract4dgui/director.py +++ b/fract4dgui/director.py @@ -221,9 +221,7 @@ def generate(self,create_avi=True): 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, self) res=png_gen.show() @@ -711,7 +709,11 @@ def onResponse(self,widget,id): self.hide() elif id == DirectorDialog.RESPONSE_RENDER: self.animation.set_avi_file(self.txt_temp_avi.get_text()) - self.generate(self.converterpath is not None) + 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()