Advertisement
Guest User

4FFmpeg_v3

a guest
Nov 9th, 2014
301
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 20.91 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. from gi.repository import Gtk, GObject, Vte
  4. from gi.repository import GLib
  5. import os
  6. import sys, subprocess, shlex, re
  7. from subprocess import call
  8.  
  9.        
  10. class reuse_init(object):
  11.  
  12.  
  13.     def make_label(self, text):
  14.         label = Gtk.Label(text)
  15.         label.set_use_underline(True)
  16.         label.set_alignment(1.0, 0.5)
  17.         label.show()
  18.         return label
  19.  
  20.     def make_slider_and_spinner(self, init, min, max, step, page, digits):
  21.         controls = {'adj':Gtk.Adjustment(init, min, max, step, page), 'slider':Gtk.HScale(), 'spinner':Gtk.SpinButton()}
  22.         controls['slider'].set_adjustment(controls['adj'])
  23.         controls['slider'].set_draw_value(False)
  24.         controls['spinner'].set_adjustment(controls['adj'])
  25.         controls['spinner'].set_digits(digits)
  26.         controls['slider'].show()
  27.         controls['spinner'].show()
  28.         return controls
  29.    
  30.     def make_frame_ag(self, f_shadow_type, f_label, alig_pad, gri_row_sp, gri_colu_sp, gri_homog):
  31.         controls =  {'frame':Gtk.Frame(), 'ali':Gtk.Alignment(), 'grid':Gtk.Grid()}
  32.         controls['frame'].set_shadow_type(f_shadow_type) # 3 GTK_SHADOW_ETCHED_IN , 1 GTK_SHADOW_IN
  33.         controls['frame'].set_label(f_label)
  34.         controls['ali'].set_padding(alig_pad, alig_pad, alig_pad, alig_pad)
  35.         controls['frame'].add(controls['ali'])
  36.         controls['grid'].set_row_spacing(gri_row_sp)
  37.         controls['grid'].set_column_spacing(gri_colu_sp)
  38.         controls['grid'].set_column_homogeneous(gri_homog) # True
  39.         controls['ali'].add(controls['grid'])
  40.         controls['frame'].show()
  41.         controls['ali'].show()
  42.         controls['grid'].show()
  43.         return controls
  44.    
  45.     def make_spinbut(self, init, min, max, step, page, digits, numeric):
  46.         controls = {'adj':Gtk.Adjustment(init, min, max, step, page), 'spinner':Gtk.SpinButton()}
  47.         controls['spinner'].set_adjustment(controls['adj'])
  48.         controls['spinner'].set_digits(digits)
  49.         controls['spinner'].set_numeric(numeric)
  50.         controls['spinner'].show()
  51.         return controls
  52.    
  53.     def make_combobox(self, *vals):
  54.         controls = {'list':Gtk.ListStore(str), 'cbox':Gtk.ComboBox(), 'cellr':Gtk.CellRendererText()}
  55.         for i, v in enumerate(vals):
  56.               controls['list'].append([v])
  57.         controls['cbox'].set_model(controls['list'])      
  58.         controls['cbox'].pack_start(controls['cellr'], True)
  59.         controls['cbox'].add_attribute(controls['cellr'], "text", 0)
  60.         controls['cbox'].set_active(0)
  61.         controls['cbox'].show()
  62.         return controls
  63.    
  64. class ProgrammaGTK(reuse_init):
  65.    
  66.  
  67.     def __init__(self):
  68.        
  69.         self.window =  Gtk.Window(type=Gtk.WindowType.TOPLEVEL)
  70.         self.window.connect("destroy", self.on_window_destroy)
  71.         self.window.set_title("4FFmpeg video crop resize calculator")
  72.         self.window.set_position(1) # 1 center, 0 none,
  73.         self.window.set_border_width(20)
  74.         self.window.set_default_size(500,-1)
  75.         #----------------------------------
  76.        
  77.         # deinterlace -----------------------
  78.         self.make_deinterlace()
  79.    
  80.         # scale ------------------------------
  81.         self.make_scale()
  82.  
  83.         # crop ----------------------------------------
  84.         self.make_crop()    
  85.        
  86.         # denoise ----------------------------
  87.         self.make_denoise()
  88.                
  89.         # output------------------------
  90.         self.frame_out = self.make_frame_ag(3, '  FFmpeg video filter string (select and copy):  ' , 12, 8, 8, True)
  91.         self.labelout = Gtk.Label("")
  92.         self.labelout.set_selectable(1)
  93.         self.frame_out['grid'].attach(self.labelout, 0, 0, 1 ,1)
  94.        
  95.         # -----------------------------
  96.        
  97.         # main VBox -------------------------
  98.         self.vbox1 = Gtk.VBox()
  99.         self.vbox1.set_spacing(12)
  100.        
  101.         # add deinterlace frame
  102.         self.vbox1.pack_start(self.f_deint['frame'], 1, 0, 0)
  103.        
  104.         # add crop frame -------------------
  105.         self.vbox1.pack_start(self.frame_crop['frame'], 1, 0, 0)
  106.    
  107.         # add scale frame------------------
  108.         self.vbox1.pack_start(self.frame_scale['frame'], 1, 0, 0)
  109.        
  110.         # add denoise frame------------------------
  111.         self.vbox1.pack_start(self.frame_dn['frame'], 1, 0, 0)
  112.        
  113.         #test-------------------
  114.         #self.f_test = self.make_frame_ag(3, "TEST" , 6, 8, 8, True)
  115.         #self.label_test = Gtk.Label("TEST")
  116.         #self.f_test['grid'].attach(self.label_test, 0, 0, 1, 1)
  117.  
  118.         #self.vbox1.pack_start(self.f_test['frame'], 1, 0, 0)
  119.         #-------------------------------------
  120.  
  121.         # add output frame ---------------------
  122.         self.vbox1.pack_start(self.frame_out['frame'], 1, 0, 0)
  123.        
  124.         # parte bassa con terminale --------------------------
  125.         self.make_low_part()
  126.        
  127.         # add low part-----------------
  128.         self.vbox1.pack_start(self.gridterm, 1, 1, 0)
  129.        
  130.         #------------------
  131.         self.window.add (self.vbox1)
  132.         #---------------------------------
  133.         self.window.show_all()
  134.        
  135.         # inizializza variabili
  136.         self.deint_str = ""
  137.         self.crop_str = ""
  138.         self.scalestr = ""
  139.         self.dn_str = ""
  140.         self.filter_test_str = ""
  141.         self.video_width = "0"
  142.         self.video_height = "0"
  143.                
  144.     def make_deinterlace(self):
  145.         self.f_deint = self.make_frame_ag(3, "" , 6, 8, 8, True)
  146.         self.checkdeint = Gtk.CheckButton("deinterlace")
  147.         self.checkdeint.connect("toggled", self.on_deint_clicked)
  148.         self.f_deint['grid'].attach(self.checkdeint, 0, 0, 1, 1)
  149.        
  150.         self.box_deint = self.make_combobox(
  151.           "yadif",
  152.           "postprocessing linear blend"
  153.           )
  154.         self.f_deint['grid'].attach(self.box_deint['cbox'], 1, 0, 1, 1)
  155.         self.box_deint['cbox'].connect("changed", self.on_deint_clicked)
  156.    
  157.     def make_scale(self):
  158.         self.frame_scale = self.make_frame_ag(3, '  scale  ' , 6, 8, 8, True)
  159.         # ------------------------------
  160.         self.box1 = self.make_combobox(
  161.           "None",
  162.           "W",
  163.           "H"
  164.           )
  165.         self.frame_scale['grid'].attach(self.box1['cbox'], 0, 0, 1, 1)
  166.         self.box1['cbox'].connect("changed", self.on_scale_clicked)
  167.         #------------------------------------
  168.         self.box2 = self.make_combobox(
  169.           "16/9",
  170.           "4/3"
  171.           )
  172.         self.frame_scale['grid'].attach(self.box2['cbox'], 0, 1, 1, 1)
  173.         self.box2['cbox'].connect("changed", self.on_scale_clicked)
  174.         #-----------------------------------------
  175.         self.size_spinner = self.make_slider_and_spinner(720, 200, 2000, 1, 10, 0)
  176.         self.frame_scale['grid'].attach(self.size_spinner['slider'], 1, 0, 1, 1)
  177.         self.frame_scale['grid'].attach(self.size_spinner['spinner'], 2, 0, 1, 1)
  178.         self.size_spinner['slider'].set_size_request(150,-1)
  179.         self.size_spinner['adj'].set_value(720.001) #mettere decimali se no non si vede
  180.         self.size_spinner['adj'].connect("value-changed", self.on_scale_clicked)
  181.         #-------------------------------------------------------
  182.         self.check = Gtk.CheckButton("lanczos")
  183.         self.check.connect("toggled", self.on_scale_clicked)
  184.         self.frame_scale['grid'].attach(self.check, 2, 1, 1, 1)
  185.        
  186.     def make_crop(self):
  187.         self.frame_crop = self.make_frame_ag(3, '' , 6, 2, 0, True)
  188.         # -------
  189.         self.checkcr = Gtk.CheckButton("crop")
  190.         self.checkcr.connect("toggled", self.on_crop_clicked)
  191.         self.frame_crop['grid'].attach(self.checkcr, 0, 0, 1, 1)
  192.         #----------
  193.         self.labelcropinw = self.make_label("input W: ")
  194.         self.frame_crop['grid'].attach(self.labelcropinw, 1, 0, 1, 1)
  195.         self.labelcropinh = self.make_label("input H: ")
  196.         self.frame_crop['grid'].attach(self.labelcropinh, 1, 1, 1, 1)
  197.         #
  198.         self.spincw = self.make_spinbut(640 , 100 , 1920 , 10 , 1 , 0, True )
  199.         self.spincw["adj"].connect("value-changed", self.on_crop_clicked)
  200.         self.frame_crop['grid'].attach(self.spincw["spinner"], 2, 0, 1, 1)
  201.         #
  202.         self.spinch = self.make_spinbut(480 , 100 , 1280 , 10 , 1 , 0, True )
  203.         self.spinch["adj"].connect("value-changed", self.on_crop_clicked)
  204.         self.frame_crop['grid'].attach(self.spinch["spinner"], 2, 1, 1, 1)
  205.         #
  206.        
  207.         self.labelcroptop = self.make_label("crop Top: ")
  208.         self.frame_crop['grid'].attach(self.labelcroptop, 3, 0, 1, 1)
  209.         self.labelcropbot = self.make_label("crop Bottom: ")
  210.         self.frame_crop['grid'].attach(self.labelcropbot, 3, 1, 1, 1)
  211.         #
  212.        
  213.         self.spinct = self.make_spinbut(0 , 0 , 600 , 1 , 10 , 0, True )
  214.         self.spinct["adj"].connect("value-changed", self.on_crop_clicked)
  215.         self.frame_crop['grid'].attach(self.spinct["spinner"], 4, 0, 1, 1)
  216.         #
  217.         self.spincb = self.make_spinbut(0 , 0 , 600 , 1 , 10 , 0, True )
  218.         self.spincb["adj"].connect("value-changed", self.on_crop_clicked)
  219.         self.frame_crop['grid'].attach(self.spincb["spinner"], 4, 1, 1, 1)
  220.         #
  221.        
  222.         self.labelcroplef = self.make_label("crop Left: ")
  223.         self.frame_crop['grid'].attach(self.labelcroplef, 5, 0, 1, 1)
  224.         self.labelcroprig = self.make_label("crop Right: ")
  225.         self.frame_crop['grid'].attach(self.labelcroprig, 5, 1, 1, 1)
  226.         #
  227.         self.spincl = self.make_spinbut(0 , 0 , 600 , 1 , 10 , 0, True )
  228.         self.spincl["adj"].connect("value-changed", self.on_crop_clicked)
  229.         self.frame_crop['grid'].attach(self.spincl["spinner"], 6, 0, 1, 1)
  230.         #
  231.         self.spincr = self.make_spinbut(0 , 0 , 600 , 1 , 10 , 0, True )
  232.         self.spincr["adj"].connect("value-changed", self.on_crop_clicked)
  233.         self.frame_crop['grid'].attach(self.spincr["spinner"], 6, 1, 1, 1)
  234.        
  235.        
  236.     def make_denoise(self):
  237.         self.frame_dn = self.make_frame_ag(3, '' , 6, 8, 8, True)
  238.         # ----------------------
  239.         self.checkdn = Gtk.CheckButton("denoise")
  240.         self.checkdn.connect("toggled", self.on_dn_clicked)
  241.         self.frame_dn['grid'].attach(self.checkdn, 0, 0, 1, 1)
  242.         # -------------------------
  243.         self.labeldn = Gtk.Label("hqdn3d:")
  244.         self.labeldn.set_alignment(1.0, 0.5)
  245.         self.frame_dn['grid'].attach(self.labeldn, 1, 0, 1, 1)
  246.         #----------------------------------------
  247.         self.spindn = self.make_spinbut(4 , 2 , 8 , 1 , 10 , 0, True )
  248.         self.spindn["adj"].connect("value-changed", self.on_dn_clicked)
  249.         self.frame_dn['grid'].attach(self.spindn["spinner"], 2, 0, 1, 1)
  250.        
  251.     def make_low_part(self):
  252.         # grid
  253.         self.gridterm = Gtk.Grid()
  254.         self.gridterm.set_row_spacing(2)
  255.         self.gridterm.set_column_spacing(2)
  256.         self.gridterm.set_column_homogeneous(True) # True
  257.         # filechooserbutton
  258.         self.filechooserbutton = Gtk.FileChooserButton(title="FileChooserButton")
  259.         #
  260.         #self.filechooserbutton.set_action(2) # 0 open file, 1 save file, 2 select folder, 3 create folder
  261.         #
  262.         self.filechooserbutton.connect("file-set", self.file_changed)  
  263.         # terminal
  264.         self.terminal     = Vte.Terminal()
  265.         self.terminal.fork_command_full(
  266.             Vte.PtyFlags.DEFAULT, #default is fine
  267.             os.getcwd(), #where to start the command?   os.environ['HOME']
  268.             ["/bin/sh"], #where is the emulator?
  269.             [], #it's ok to leave this list empty
  270.             GLib.SpawnFlags.DO_NOT_REAP_CHILD,
  271.             None, #at least None is required
  272.             None,
  273.             )
  274.         self.scroller = Gtk.ScrolledWindow()
  275.         self.scroller.set_hexpand(True)
  276.         self.scroller.set_vexpand(True)
  277.         self.scroller.add(self.terminal)
  278.         self.gridterm.attach(self.scroller, 0, 1, 6, 1)
  279.         #ff command button
  280.         self.butplay = Gtk.Button(label="FFplay")
  281.         self.butplay.connect("clicked", self.on_butplay_clicked)
  282.         self.butplay.set_sensitive(False)
  283.         self.butprobe = Gtk.Button(label="FFprobe")
  284.         self.butprobe.connect("clicked", self.on_butprobe_clicked)
  285.         self.butprobe.set_sensitive(False)
  286.         self.butcrode = Gtk.Button(label="Crop detect")
  287.         self.butcrode.connect("clicked", self.on_butcrode_clicked)
  288.         self.butcrode.set_sensitive(False)
  289.         self.buttest = Gtk.Button(label="Test")
  290.         self.buttest.connect("clicked", self.on_buttest_clicked)
  291.         self.buttest.set_sensitive(False)
  292.         self.buttestspl = Gtk.Button(label="Test split")
  293.         self.buttestspl.connect("clicked", self.on_buttestspl_clicked)
  294.         self.buttestspl.set_sensitive(False)
  295.        
  296.         self.gridterm.attach(self.filechooserbutton, 0, 0, 1, 1)
  297.         self.gridterm.attach(self.butprobe, 1, 0, 1, 1)
  298.         self.gridterm.attach(self.butplay, 2, 0, 1, 1)
  299.         self.gridterm.attach(self.butcrode, 3, 0, 1, 1)
  300.         self.gridterm.attach(self.buttest, 4, 0, 1, 1)
  301.         self.gridterm.attach(self.buttestspl, 5, 0, 1, 1)
  302.    
  303.     # signal handlers -----------------------------------------------
  304.     def on_window_destroy(self, *args):
  305.         Gtk.main_quit(*args)
  306.    
  307.     def on_scale_clicked(self, button):
  308.         if (self.box1['cbox'].get_active() != 0):
  309.           self.calcola_scale()
  310.         else:
  311.           self.scalestr = ""
  312.           self.outfilter()
  313.          
  314.     def on_deint_clicked(self, button):
  315.         self.deint_str = ""
  316.         if self.checkdeint.get_active():
  317.             deint_val = self.box_deint['cbox'].get_active()
  318.             if (deint_val == 0):
  319.             self.deint_str = "yadif"
  320.             elif (deint_val == 1):
  321.             self.deint_str = "pp=lb"    
  322.         self.outfilter()
  323.            
  324.     def on_dn_clicked(self, button):
  325.         self.dn_str = ""
  326.         if self.checkdn.get_active():
  327.             dn_val = int( self.spindn['adj'].get_value() )
  328.             self.dn_str = "hqdn3d=" + str(dn_val)
  329.         self.outfilter()
  330.            
  331.     def on_crop_clicked(self, button):
  332.         ok = 0
  333.         self.crop_str = ""
  334.         if self.checkcr.get_active():
  335.             ok = 1
  336.         if ok:
  337.             inW = int(self.spincw['adj'].get_value())
  338.             inH = int(self.spinch['adj'].get_value())
  339.             crT = int(self.spinct['adj'].get_value())
  340.             crB = int(self.spincb['adj'].get_value())
  341.             crL = int(self.spincl['adj'].get_value())
  342.             crR = int(self.spincr['adj'].get_value())  
  343.             finW = inW - crL - crR
  344.             finH = inH - crT - crB
  345.             if ( (finW < 100) or (finH < 100) ):
  346.             self.crop_str = (" ** invalid crop ** ")
  347.             else:
  348.             self.crop_str = "crop=" + str(finW) + ":" \
  349.               + str(finH) + ":" \
  350.               + str(crL)  + ":" \
  351.               + str(crT)
  352.         if (self.box2['cbox'].get_active() != 2):
  353.             self.outfilter()
  354.         else:
  355.             self.calcola_scale()
  356.        
  357.     def file_changed(self, filechooserbutton):      
  358.         if self.filechooserbutton.get_filename():
  359.           self.butplay.set_sensitive(True)
  360.           self.butprobe.set_sensitive(True)
  361.           self.butcrode.set_sensitive(True)
  362.           self.buttest.set_sensitive(True)
  363.           self.buttestspl.set_sensitive(True)
  364.           # need ffprobe
  365.           filename = self.filechooserbutton.get_filename()
  366.           cmnd = ['ffprobe', '-show_format', '-show_streams', '-pretty', '-loglevel', 'quiet', filename]
  367.           p = subprocess.Popen(cmnd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  368.           out, err =  p.communicate()
  369.           for line in out.split('\n'):
  370.               line = line.strip()
  371.               if line.startswith('width='):
  372.                 s = line
  373.                 width = s.split('width=', 1)
  374.                 #print "Video pixel width is: %s" % width[1]
  375.                 self.video_width = width[1]
  376.                 self.spincw['adj'].set_value(float(self.video_width)) #mettere decimali se no non si vede
  377.                 self.spincw['spinner'].set_sensitive(False)
  378.               elif line.startswith('height='):
  379.                 s = line
  380.                 height = s.split('height=', 1)
  381.                 #print "Video pixel height is: %s" % height[1]
  382.                 self.video_height = height[1]    
  383.                 self.spinch['adj'].set_value(float(self.video_height)) #mettere decimali se no non si vede
  384.                 self.spinch['spinner'].set_sensitive(False)
  385.                 self.box2['list'].append(["as input"])
  386.          
  387.     def on_butplay_clicked(self, button):
  388.         self.command = "ffplay \"" + self.filechooserbutton.get_filename() + "\"" +"\n"
  389.         length = len(self.command)
  390.         self.terminal.feed_child(self.command, length)
  391.        
  392.     def on_butprobe_clicked(self, button):
  393.         self.command = "ffprobe \"" + self.filechooserbutton.get_filename() + "\""  + " 2>&1 | grep 'Input\|Duration\|Stream'" + "\n"
  394.         length = len(self.command)
  395.         self.terminal.feed_child(self.command, length)
  396.        
  397.          
  398.        
  399.     def on_butcrode_clicked(self, button):
  400.         self.command = "ffplay \"" + self.filechooserbutton.get_filename() + "\"" + " -vf cropdetect" +"\n"
  401.         length = len(self.command)
  402.         self.terminal.feed_child(self.command, length)
  403.        
  404.     def on_buttest_clicked(self, button):
  405.         self.command = "ffplay \"" + self.filechooserbutton.get_filename() + "\"" + " -vf " + self.filter_test_str +"\n"
  406.         length = len(self.command)
  407.         self.terminal.feed_child(self.command, length)
  408.        
  409.     def on_buttestspl_clicked(self, button):
  410.         self.command = "ffplay \"" + self.filechooserbutton.get_filename() + "\"" + " -vf \"split[a][b]; [a]pad=iw:(ih*2)+4:color=888888 [src]; [b]" + self.filter_test_str + " [filt]; [src][filt]overlay=((main_w - w)/2):main_h/2\"" + "\n"
  411.         length = len(self.command)
  412.         self.terminal.feed_child(self.command, length)
  413.    
  414.     # -------------------------------------    
  415.     def calcola_scale(self):
  416.         self.scalestr = ""
  417.         numero = self.size_spinner['adj'].get_value()
  418.         if ( (int(numero) % 2) != 0):
  419.           numero = int(numero) + 1
  420.         else:
  421.           numero = int(numero)    
  422.         dar = self.box2['cbox'].get_active()
  423.         lato = self.box1['cbox'].get_active()      
  424.         if (dar == 1): # 4/3
  425.           if (lato == 1):
  426.             n2 = numero/4*3
  427.           else:
  428.             n2 = numero/3*4
  429.           setdar = ",setdar=4/3,setsar=1/1"
  430.         elif (dar == 0): # 16/9
  431.           if (lato == 1):
  432.             n2 = numero/16*9
  433.           else:
  434.             n2 = numero/9*16
  435.           setdar = ",setdar=16/9,setsar=1/1"    
  436.         elif (dar == 2): #1/1 same as input  
  437.           w = int(self.video_width)
  438.           h = int(self.video_height)
  439.           if self.checkcr.get_active():
  440.             crT = int(self.spinct['adj'].get_value())
  441.             crB = int(self.spincb['adj'].get_value())
  442.             crL = int(self.spincl['adj'].get_value())
  443.             crR = int(self.spincr['adj'].get_value())
  444.             h = h - crT - crB
  445.             w = w - crL - crR
  446.           if (lato == 1): # h          
  447.             n2 = (float(h)/(w/float(numero)))  
  448.           else:
  449.             n2 = (float(w)/(h/float(numero)))
  450.           setdar = ""
  451.         if ( (int(n2) % 2) != 0):
  452.           n2 = int(n2) + 1
  453.         else:
  454.           n2 = int(n2)          
  455.         self.scalestr = "scale="
  456.         if (lato == 1):
  457.           self.scalestr = self.scalestr + str(numero) + ":" + str(n2)
  458.         else:
  459.           self.scalestr = self.scalestr + str(n2) + ":" + str(numero)            
  460.         lanc = self.check.get_active()  
  461.         if lanc:
  462.           self.scalestr = self.scalestr + ":flags=lanczos" + setdar  
  463.         else:
  464.           self.scalestr = self.scalestr + setdar
  465.         self.outfilter()
  466.              
  467.     def outfilter(self):
  468.         final_str = ""
  469.         if (self.deint_str != ""):
  470.             final_str = final_str + self.deint_str
  471.         if (self.crop_str != ""):
  472.             if (final_str == ""):
  473.             final_str = final_str + self.crop_str
  474.             else:
  475.             final_str = final_str + "," + self.crop_str
  476.         if (self.scalestr != ""):
  477.             if (final_str == ""):
  478.             final_str = final_str + self.scalestr
  479.             else:
  480.             final_str = final_str + "," + self.scalestr
  481.         if (self.dn_str != ""):
  482.             if (final_str == ""):
  483.             final_str = final_str + self.dn_str
  484.             else:
  485.             final_str = final_str + "," + self.dn_str      
  486.         self.filter_test_str = final_str
  487.         if (final_str != ""):
  488.             final_str = "-vf " + final_str
  489.            
  490.         self.labelout.set_text(final_str)
  491.        
  492.        
  493.     def main(self):
  494.         Gtk.main()
  495.  
  496.        
  497.    
  498.  
  499. if __name__ == "__main__":
  500.     hwg = ProgrammaGTK()
  501.     Gtk.main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement