Now stores and recovers all the file properties
More changes in the interface to allow a better workflow Started support for Matroska and H.264
This commit is contained in:
parent
4e6a269475
commit
d59fc7c0e7
15 changed files with 1495 additions and 418 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -4,9 +4,12 @@
|
||||||
<requires lib="gtk+" version="3.10"/>
|
<requires lib="gtk+" version="3.10"/>
|
||||||
<object class="GtkWindow" id="wselect_disk">
|
<object class="GtkWindow" id="wselect_disk">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
|
<property name="resizable">False</property>
|
||||||
|
<property name="modal">True</property>
|
||||||
<property name="window_position">center</property>
|
<property name="window_position">center</property>
|
||||||
<property name="icon">devede.svg</property>
|
<property name="icon">devede.svg</property>
|
||||||
<signal name="delete-event" handler="on_wselect_disk_delete_event" swapped="no"/>
|
<property name="deletable">False</property>
|
||||||
|
<property name="has_resize_grip">False</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="box1">
|
<object class="GtkBox" id="box1">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
|
@ -261,6 +264,53 @@ Creates files compliant with DivX home players</property>
|
||||||
<property name="position">5</property>
|
<property name="position">5</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="button_mkv">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="on_button_mkv_clicked" swapped="no"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="box7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="image6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="pixbuf">icon_mkv.png</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes"><b>Matroska / H.264</b>
|
||||||
|
Creates H.264 files in a MKV container</property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">4</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">6</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
||||||
BIN
data/pixmaps/icon_mkv.png
Normal file
BIN
data/pixmaps/icon_mkv.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -26,14 +26,6 @@ class add_files:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.config = devede.configuration_data.configuration.get_config()
|
self.config = devede.configuration_data.configuration.get_config()
|
||||||
self.show_title_options = True
|
|
||||||
|
|
||||||
def set_type(self,disc_type):
|
|
||||||
|
|
||||||
if (disc_type == "dvd"):
|
|
||||||
self.show_title_options = True
|
|
||||||
else:
|
|
||||||
self.show_title_options = False
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Copyright 2014 (C) Raster Software Vigo (Sergio Costas)
|
# Copyright 2014 (C) Raster Software Vigo (Sergio Costas)
|
||||||
#
|
#
|
||||||
# This file is part of DeVeDe-NG
|
# This file is part of DeVeDe-NG
|
||||||
|
|
@ -15,5 +17,14 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
def test():
|
import subprocess
|
||||||
print("Test function2")
|
|
||||||
|
import devede.configuration_data
|
||||||
|
|
||||||
|
class avconv_converter:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.config = devede.configuration_data.configuration.get_config()
|
||||||
|
|
||||||
|
|
||||||
48
src/devede/check_binaries.py
Normal file
48
src/devede/check_binaries.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright 2014 (C) Raster Software Vigo (Sergio Costas)
|
||||||
|
#
|
||||||
|
# This file is part of DeVeDe-NG
|
||||||
|
#
|
||||||
|
# DeVeDe-NG is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# DeVeDe-NG 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import devede.configuration_data
|
||||||
|
|
||||||
|
class check_binaries:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.config = devede.configuration_data.configuration.get_config()
|
||||||
|
|
||||||
|
self.check_mplayer()
|
||||||
|
self.check_mpv()
|
||||||
|
|
||||||
|
|
||||||
|
def check_mplayer(self):
|
||||||
|
|
||||||
|
handle = subprocess.Popen(["mplayer","-v"])
|
||||||
|
if 0==handle.wait():
|
||||||
|
self.config.mplayer_available = True
|
||||||
|
else:
|
||||||
|
self.config.mplayer_available = False
|
||||||
|
|
||||||
|
def check_mpv(self):
|
||||||
|
|
||||||
|
handle = subprocess.Popen(["mpv","-v"])
|
||||||
|
if 0==handle.wait():
|
||||||
|
self.config.mpv_available = True
|
||||||
|
else:
|
||||||
|
self.config.mpv_available = False
|
||||||
66
src/devede/choose_disc_type.py
Normal file
66
src/devede/choose_disc_type.py
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Copyright 2014 (C) Raster Software Vigo (Sergio Costas)
|
||||||
|
#
|
||||||
|
# This file is part of DeVeDe-NG
|
||||||
|
#
|
||||||
|
# DeVeDe-NG is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# DeVeDe-NG 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
from gi.repository import Gtk,GObject
|
||||||
|
import os
|
||||||
|
|
||||||
|
import devede.configuration_data
|
||||||
|
|
||||||
|
class choose_disc_type(GObject.GObject):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.config = devede.configuration_data.configuration.get_config()
|
||||||
|
|
||||||
|
builder = Gtk.Builder()
|
||||||
|
builder.set_translation_domain(self.config.gettext_domain)
|
||||||
|
|
||||||
|
builder.add_from_file(os.path.join(self.config.glade,"wselect_disk.ui"))
|
||||||
|
builder.connect_signals(self)
|
||||||
|
self.wask_window = builder.get_object("wselect_disk")
|
||||||
|
self.wask_window.show_all()
|
||||||
|
|
||||||
|
def set_type(self,disc_type):
|
||||||
|
|
||||||
|
self.config.set_disc_type(disc_type)
|
||||||
|
self.wask_window.hide()
|
||||||
|
self.wask_window.destroy()
|
||||||
|
self.wask_window = None
|
||||||
|
|
||||||
|
def on_button_dvd_clicked(self,b):
|
||||||
|
|
||||||
|
self.set_type("dvd")
|
||||||
|
|
||||||
|
def on_button_vcd_clicked(self,b):
|
||||||
|
|
||||||
|
self.set_type("vcd")
|
||||||
|
|
||||||
|
def on_button_svcd_clicked(self,b):
|
||||||
|
|
||||||
|
self.set_type("svcd")
|
||||||
|
|
||||||
|
def on_button_cvd_clicked(self,b):
|
||||||
|
|
||||||
|
self.set_type("cvd")
|
||||||
|
|
||||||
|
def on_button_divx_clicked(self,b):
|
||||||
|
|
||||||
|
self.set_type("divx")
|
||||||
|
|
||||||
|
def on_button_mkv_clicked(self,b):
|
||||||
|
|
||||||
|
self.set_type("mkv")
|
||||||
|
|
@ -17,38 +17,45 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
from gi.repository import GObject
|
||||||
import os
|
import os
|
||||||
|
|
||||||
class configuration:
|
class configuration(GObject.GObject):
|
||||||
|
|
||||||
current_configuration = None
|
current_configuration = None
|
||||||
|
__gsignals__ = {'disc_type': (GObject.SIGNAL_RUN_FIRST, None,(str,))}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_config():
|
def get_config():
|
||||||
if configuration.current_configuration == None:
|
if configuration.current_configuration == None:
|
||||||
configuration.current_configuration = configuration()
|
configuration.current_configuration = configuration()
|
||||||
|
if (configuration.current_configuration.fill_config()):
|
||||||
|
configuration.current_configuration = None
|
||||||
return configuration.current_configuration
|
return configuration.current_configuration
|
||||||
|
|
||||||
def __init__(self):
|
def fill_config(self):
|
||||||
|
|
||||||
|
GObject.GObject.__init__(self)
|
||||||
|
|
||||||
self.error = False
|
|
||||||
is_local = None
|
is_local = None
|
||||||
|
self.log = ""
|
||||||
|
self.disc_type = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.stat("/usr/share/devede_ng/wselect_disk.ui")
|
os.stat("/usr/share/devede_ng/wselect_disk.ui")
|
||||||
is_local = False
|
is_local = False
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if is_local == None:
|
if is_local == None:
|
||||||
try:
|
try:
|
||||||
os.stat("/usr/local/share/devede_ng/wselect_disk.ui")
|
os.stat("/usr/local/share/devede_ng/wselect_disk.ui")
|
||||||
is_local = True
|
is_local = True
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if is_local == None:
|
if is_local == None:
|
||||||
self.error = True
|
return True
|
||||||
else:
|
else:
|
||||||
if (is_local):
|
if (is_local):
|
||||||
# locales must be always at /usr/share/locale because Gtk.Builder always search there
|
# locales must be always at /usr/share/locale because Gtk.Builder always search there
|
||||||
|
|
@ -74,6 +81,11 @@ class configuration:
|
||||||
self.final_folder = None
|
self.final_folder = None
|
||||||
self.sub_language = None
|
self.sub_language = None
|
||||||
self.sub_codepage = None
|
self.sub_codepage = None
|
||||||
|
self.film_analizer = None
|
||||||
|
self.film_player = None
|
||||||
|
self.film_converter = None
|
||||||
|
self.menu_converter = None
|
||||||
|
self.subtitles_font_size = 28
|
||||||
|
|
||||||
config_path = os.path.join(os.environ.get("HOME"),".devede")
|
config_path = os.path.join(os.environ.get("HOME"),".devede")
|
||||||
try:
|
try:
|
||||||
|
|
@ -87,29 +99,54 @@ class configuration:
|
||||||
if linea[:13]=="video_format:":
|
if linea[:13]=="video_format:":
|
||||||
if linea[13:].strip() =="pal":
|
if linea[13:].strip() =="pal":
|
||||||
self.PAL=True
|
self.PAL=True
|
||||||
continue
|
elif linea[13:].strip()=="ntsc":
|
||||||
if linea[13:].strip()=="ntsc":
|
|
||||||
self.PAL=False
|
self.PAL=False
|
||||||
continue
|
continue
|
||||||
if linea[:12]=="temp_folder:":
|
if linea[:12]=="temp_folder:":
|
||||||
self.tmp_folder=linea[12:].strip()
|
self.tmp_folder=linea[12:].strip()
|
||||||
|
continue
|
||||||
if linea[:10]=="multicore:":
|
if linea[:10]=="multicore:":
|
||||||
if linea[10:].strip()=="1":
|
if linea[10:].strip()=="1":
|
||||||
self.multicore = False
|
self.multicore = False
|
||||||
else:
|
else:
|
||||||
self.multicore = True
|
self.multicore = True
|
||||||
|
continue
|
||||||
if linea[:13]=="final_folder:":
|
if linea[:13]=="final_folder:":
|
||||||
self.final_folder=linea[13:].strip()
|
self.final_folder=linea[13:].strip()
|
||||||
|
continue
|
||||||
if linea[:13]=="sub_language:":
|
if linea[:13]=="sub_language:":
|
||||||
self.sub_language=linea[13:].strip()
|
self.sub_language=linea[13:].strip()
|
||||||
|
continue
|
||||||
if linea[:13]=="sub_codepage:":
|
if linea[:13]=="sub_codepage:":
|
||||||
self.sub_codepage=linea[13:].strip()
|
self.sub_codepage=linea[13:].strip()
|
||||||
|
continue
|
||||||
|
if linea[:14]=="film_analizer:":
|
||||||
|
self.film_analizer = linea[14:].strip()
|
||||||
|
continue
|
||||||
|
if linea[:12]=="film_player:":
|
||||||
|
self.film_player = linea[12:].strip()
|
||||||
|
continue
|
||||||
|
if linea[:15]=="film_converter:":
|
||||||
|
self.film_converter = linea[15:].strip()
|
||||||
|
continue
|
||||||
|
if linea[:15]=="menu_converter:":
|
||||||
|
self.menu_converter = linea[15:].strip()
|
||||||
|
continue
|
||||||
|
if linea[:19]=="subtitle_font_size:":
|
||||||
|
self.subtitles_font_size = int(linea[19:].strip())
|
||||||
config_data.close()
|
config_data.close()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def set_disc_type(self,disc_type):
|
||||||
|
|
||||||
|
self.disc_type = disc_type
|
||||||
|
self.emit('disc_type',disc_type)
|
||||||
|
|
||||||
def save_config(self):
|
def save_config(self):
|
||||||
|
|
||||||
config_path = os.path.join(os.environ.get("HOME"),".devede")
|
config_path = os.path.join(os.environ.get("HOME"),".devede")
|
||||||
try:
|
try:
|
||||||
config_data = open(config_path,"w")
|
config_data = open(config_path,"w")
|
||||||
|
|
@ -131,6 +168,29 @@ class configuration:
|
||||||
config_data.write("sub_language:"+str(self.sub_language)+"\n")
|
config_data.write("sub_language:"+str(self.sub_language)+"\n")
|
||||||
if (self.sub_codepage != None):
|
if (self.sub_codepage != None):
|
||||||
config_data.write("sub_codepage:"+str(self.sub_codepage)+"\n")
|
config_data.write("sub_codepage:"+str(self.sub_codepage)+"\n")
|
||||||
|
if (self.film_analizer != None):
|
||||||
|
config_data.write("film_analizer:"+str(self.film_analizer)+"\n")
|
||||||
|
if (self.film_player != None):
|
||||||
|
config_data.write("film_player:"+str(self.film_player)+"\n")
|
||||||
|
if (self.film_converter != None):
|
||||||
|
config_data.write("film_converter:"+str(self.film_converter)+"\n")
|
||||||
|
if (self.menu_converter != None):
|
||||||
|
config_data.write("menu_converter:"+str(self.menu_converter)+"\n")
|
||||||
|
config_data.write("subtitle_font_size:"+str(self.subtitles_font_size)+"\n")
|
||||||
config_data.close()
|
config_data.close()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def append_log(self,data,cr = True):
|
||||||
|
|
||||||
|
self.log+=data
|
||||||
|
if (cr):
|
||||||
|
self.log += "\n"
|
||||||
|
|
||||||
|
def clear_log(self):
|
||||||
|
|
||||||
|
self.log = ""
|
||||||
|
|
||||||
|
def get_log(self):
|
||||||
|
|
||||||
|
return self.log
|
||||||
115
src/devede/converter.py
Normal file
115
src/devede/converter.py
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
# Copyright 2014 (C) Raster Software Vigo (Sergio Costas)
|
||||||
|
#
|
||||||
|
# This file is part of DeVeDe-NG
|
||||||
|
#
|
||||||
|
# DeVeDe-NG is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# DeVeDe-NG 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
import devede.configuration_data
|
||||||
|
import devede.mpv_converter
|
||||||
|
|
||||||
|
class converter:
|
||||||
|
|
||||||
|
current_converter = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_converter():
|
||||||
|
if converter.current_converter == None:
|
||||||
|
converter.current_converter = converter.converter()
|
||||||
|
return converter.current_converter
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.config = devede.configuration_data.configuration.get_config()
|
||||||
|
self.c = [devede.mpv_converter.mpv_converter]
|
||||||
|
|
||||||
|
self.analizers = {}
|
||||||
|
self.default_analizer = None
|
||||||
|
self.players = {}
|
||||||
|
self.default_player = None
|
||||||
|
self.converters = {}
|
||||||
|
self.default_converter = None
|
||||||
|
self.menuers = {}
|
||||||
|
self.default_menuer = None
|
||||||
|
|
||||||
|
for element in self.c:
|
||||||
|
e = element()
|
||||||
|
if (e.check_is_installed() == False):
|
||||||
|
continue
|
||||||
|
name = e.display_name
|
||||||
|
if (e.supports_analize):
|
||||||
|
self.analizers[name] = element
|
||||||
|
if (self.default_analizer == None):
|
||||||
|
self.default_analizer = element
|
||||||
|
if (e.supports_play):
|
||||||
|
self.players[name] = element
|
||||||
|
if (self.default_player == None):
|
||||||
|
self.default_player = element
|
||||||
|
if (e.supports_convert):
|
||||||
|
self.converters[name] = element
|
||||||
|
if (self.default_converter == None):
|
||||||
|
self.default_converter = element
|
||||||
|
if (e.supports_menu):
|
||||||
|
self.menuers[name] = element
|
||||||
|
if (self.default_menuer == None):
|
||||||
|
self.default_menuer = element
|
||||||
|
|
||||||
|
|
||||||
|
def get_needed_programs(self):
|
||||||
|
""" returns a tupla with four lists. When a list is NONE, there are installed in the system
|
||||||
|
programs that covers the needs for that group; when not, it contains the programs valid
|
||||||
|
to cover the needs for that group.
|
||||||
|
The groups are, in this order: ANALIZERS, PLAYERS, CONVERTERS, MENUERS
|
||||||
|
(menuers are the programs that creates the mpeg files for menus)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if (self.default_analizer != None):
|
||||||
|
analizers = None
|
||||||
|
else:
|
||||||
|
analizers = []
|
||||||
|
if (self.default_player != None):
|
||||||
|
players = None
|
||||||
|
else:
|
||||||
|
players = []
|
||||||
|
if (self.default_converter != None):
|
||||||
|
converters = None
|
||||||
|
else:
|
||||||
|
converters = []
|
||||||
|
if (self.default_menuer != None):
|
||||||
|
menuers = None
|
||||||
|
else:
|
||||||
|
menuers = []
|
||||||
|
|
||||||
|
for element in self.c:
|
||||||
|
e = element()
|
||||||
|
name = e.display_name
|
||||||
|
if (e.supports_analize) and (analizers != None):
|
||||||
|
analizers.append(name)
|
||||||
|
if (e.supports_play) and (players != None):
|
||||||
|
players.append(name)
|
||||||
|
if (e.supports_convert) and (converters != None):
|
||||||
|
converters.append(name)
|
||||||
|
if (e.supports_menu) and (menuers != None):
|
||||||
|
menuers.append(name)
|
||||||
|
|
||||||
|
return ( analizers, players, converters, menuers )
|
||||||
|
|
||||||
|
|
||||||
|
def get_film_analizer(self):
|
||||||
|
""" returns a class for the desired film analizer, or the most priviledged if the desired is not installed """
|
||||||
|
|
||||||
|
if (self.config.film_analizer == None) or (self.analizers.has_key(self.config.film_analizer) == False):
|
||||||
|
return self.default_analizer
|
||||||
|
else:
|
||||||
|
return self.analizers[self.config.film_analizer]
|
||||||
32
src/devede/executor.py
Normal file
32
src/devede/executor.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright 2014 (C) Raster Software Vigo (Sergio Costas)
|
||||||
|
#
|
||||||
|
# This file is part of DeVeDe-NG
|
||||||
|
#
|
||||||
|
# DeVeDe-NG is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# DeVeDe-NG 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import devede.configuration_data
|
||||||
|
|
||||||
|
class executor:
|
||||||
|
""" This class encapsulates everything needed for launching processes """
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.config = devede.configuration_data.configuration.get_config()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,72 +17,149 @@
|
||||||
|
|
||||||
from gi.repository import Gtk,GObject
|
from gi.repository import Gtk,GObject
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import devede.configuration_data
|
import devede.configuration_data
|
||||||
|
import devede.interface_manager
|
||||||
|
import devede.converter
|
||||||
|
|
||||||
class file_movie(GObject.GObject):
|
class file_movie(devede.interface_manager.interface_manager):
|
||||||
|
|
||||||
counter2 = 0
|
__gsignals__ = {'title_changed': (GObject.SIGNAL_RUN_FIRST, None,(str,))}
|
||||||
|
|
||||||
def __init__(self,file_name):
|
def __init__(self,file_name):
|
||||||
|
|
||||||
GObject.GObject.__init__(self)
|
GObject.GObject.__init__(self)
|
||||||
self.config = devede.configuration_data.configuration.get_config()
|
devede.interface_manager.interface_manager.__init__(self)
|
||||||
self.file_name = file_name
|
|
||||||
self.title_name = os.path.splitext(os.path.basename(file_name))[0]
|
|
||||||
self.wfile_properties = None
|
|
||||||
self.model = None
|
|
||||||
self.treeiter = None
|
|
||||||
self.builder = None
|
|
||||||
|
|
||||||
self.video_rate = 0
|
|
||||||
self.audio_rate = 0
|
|
||||||
self.final_size = None
|
|
||||||
self.aspect_ratio = None
|
|
||||||
|
|
||||||
def set_type(self,disc_type = None):
|
self.wfile_properties = None
|
||||||
|
self.builder = None
|
||||||
|
|
||||||
|
self.config = devede.configuration_data.configuration.get_config()
|
||||||
|
self.set_type(None, self.config.disc_type)
|
||||||
|
self.config.connect('disc_type',self.set_type)
|
||||||
|
|
||||||
|
self.add_text("file_name", file_name)
|
||||||
|
self.add_text("title_name", os.path.splitext(os.path.basename(file_name))[0])
|
||||||
|
self.add_label("original_size",None)
|
||||||
|
self.add_label("original_length",None)
|
||||||
|
self.add_label("original_videorate",None)
|
||||||
|
self.add_label("original_audiorate",None)
|
||||||
|
self.add_label("original_aspect_ratio",None)
|
||||||
|
self.add_label("original_fps",None)
|
||||||
|
|
||||||
|
self.add_toggle("show_in_menu", True)
|
||||||
|
self.add_toggle("format_pal",self.config.PAL)
|
||||||
|
self.add_toggle("video_rate_automatic", True)
|
||||||
|
self.add_toggle("audio_rate_automatic", True)
|
||||||
|
self.add_toggle("divide_in_chapters", True)
|
||||||
|
self.add_toggle("force_subtitles", False)
|
||||||
|
self.add_toggle("mirror_horizontal", False)
|
||||||
|
self.add_toggle("mirror_vertical", False)
|
||||||
|
self.add_toggle("two_pass_encoding", True)
|
||||||
|
self.add_toggle("sound5_1", False)
|
||||||
|
self.add_toggle("copy_sound", False)
|
||||||
|
self.add_toggle("is_mpeg_ps", False)
|
||||||
|
self.add_toggle("no_reencode_audio_video", False)
|
||||||
|
if (self.disc_type == "divx") or (self.disc_type == "mkv"):
|
||||||
|
self.add_toggle("gop12", False)
|
||||||
|
else:
|
||||||
|
self.add_toggle("gop12", True)
|
||||||
|
|
||||||
|
self.add_group("final_size_pal", ["size_auto", "size_1920x1080", "size_1280x720", "size_720x576", "size_704x576", "size_480x576","size_352x576", "size_352x288"], "size_auto")
|
||||||
|
self.add_group("final_size_ntsc", ["size_auto_ntsc", "size_1920x1080_ntsc", "size_1280x720_ntsc", "size_720x480", "size_704x480", "size_480x480","size_352x480", "size_352x240"], "size_auto_ntsc")
|
||||||
|
self.add_group("aspect_ratio", ["aspect_auto", "aspect_classic", "aspect_wide"], "aspect_auto")
|
||||||
|
self.add_group("scaling", ["add_black_bars", "scale_picture" ,"cut_picture"], "add_black_bars")
|
||||||
|
self.add_group("rotation",["rotation_0","rotation_90","rotation_180","rotation_270"], "rotation_0")
|
||||||
|
self.add_group("deinterlace", ["deinterlace_none", "deinterlace_ffmpeg", "deinterlace_yadif"], "deinterlace_none")
|
||||||
|
self.add_group("actions", ["action_stop","action_play_first","action__play_previous","action_play_again","action_play_next","action_play_last"], "action_stop")
|
||||||
|
|
||||||
|
self.add_integer_adjustment("volume", 100)
|
||||||
|
if (self.disc_type == "dvd"):
|
||||||
|
self.add_integer_adjustment("video_rate", 5000)
|
||||||
|
elif (self.disc_type == "vcd"):
|
||||||
|
self.add_integer_adjustment("video_rate", 1152)
|
||||||
|
else:
|
||||||
|
self.add_integer_adjustment("video_rate", 2000)
|
||||||
|
self.add_integer_adjustment("audio_rate", 224)
|
||||||
|
self.add_integer_adjustment("subt_font_size", 28)
|
||||||
|
self.add_float_adjustment("audio_delay", 0.0)
|
||||||
|
|
||||||
|
self.add_show_hide("format_pal", ["size_pal"], ["size_ntsc"])
|
||||||
|
|
||||||
|
self.add_enable_disable("divide_in_chapters", ["chapter_size_spinbutton"], [])
|
||||||
|
self.add_enable_disable("video_rate_automatic", [], ["video_spinbutton"])
|
||||||
|
self.add_enable_disable("audio_rate_automatic", [], ["audio_spinbutton"])
|
||||||
|
self.add_enable_disable("sound5_1", ["copy_sound"], [])
|
||||||
|
|
||||||
|
self.add_enable_disable("copy_sound", [], ["audio_delay_spinbutton","audio_rate_automatic","audio_spinbutton","spinbutton_volume","scale_volume","reset_volume"])
|
||||||
|
|
||||||
|
common_elements = ["gop12","video_rate_automatic","video_spinbutton","audio_rate_automatic","audio_spinbutton","format_pal","format_ntsc","spinbutton_volume","scale_volume","reset_volume",
|
||||||
|
"size_auto", "size_1920x1080", "size_1280x720", "size_720x576", "size_704x576", "size_480x576","size_352x576", "size_352x288",
|
||||||
|
"size_auto_ntsc", "size_1920x1080_ntsc", "size_1280x720_ntsc", "size_720x480", "size_704x480", "size_480x480","size_352x480", "size_352x240",
|
||||||
|
"aspect_auto","aspect_classic","aspect_wide","mirror_horizontal","mirror_vertical","add_black_bars","scale_picture","cut_picture",
|
||||||
|
"rotation_0","rotation_90","rotation_180","rotation_270","two_pass_encoding","deinterlace_none","deinterlace_ffmpeg","deinterlace_yadif",
|
||||||
|
"audio_delay_spinbutton","sound5_1","copy_sound"]
|
||||||
|
|
||||||
|
is_mpeg_ps_list = common_elements[:]
|
||||||
|
is_mpeg_ps_list.append("no_reencode_audio_video")
|
||||||
|
is_mpeg_ps_list.append("font_size_spinbutton")
|
||||||
|
is_mpeg_ps_list.append("force_subtitles")
|
||||||
|
is_mpeg_ps_list.append("button_add_subtitles")
|
||||||
|
is_mpeg_ps_list.append("button_del_subtitles")
|
||||||
|
no_reencode_audio_video_list = common_elements[:]
|
||||||
|
no_reencode_audio_video_list.append("is_mpeg_ps")
|
||||||
|
|
||||||
|
self.add_enable_disable("is_mpeg_ps", [], is_mpeg_ps_list)
|
||||||
|
self.add_enable_disable("no_reencode_audio_video", [], no_reencode_audio_video_list)
|
||||||
|
#self.add_enable_disable("", [], [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#cv = devede.converter.converter()
|
||||||
|
#cv.get_film_data(self)
|
||||||
|
|
||||||
|
def set_type(self,obj = None,disc_type = None):
|
||||||
|
|
||||||
if (disc_type != None):
|
if (disc_type != None):
|
||||||
self.disc_type = disc_type
|
self.disc_type = disc_type
|
||||||
if (disc_type == "dvd"):
|
|
||||||
self.video_rate = 5000
|
if (disc_type == "vcd"):
|
||||||
self.audio_rate = 224
|
self.final_size_auto_width = 352
|
||||||
self.final_size = "size_auto"
|
self.final_size_auto_height_pal = 288
|
||||||
self.aspect_ratio = "aspect_auto"
|
self.final_size_auto_height_ntsc = 240
|
||||||
elif (disc_type == "vcd"):
|
self.video_rate_auto_value = 1152
|
||||||
self.video_rate = 1152
|
self.audio_rate_auto_value = 224
|
||||||
self.audio_rate = 224
|
elif (disc_type == "svcd"):
|
||||||
self.final_size = "size_352x288"
|
self.final_size_auto_width = 480
|
||||||
self.aspect_ratio = "aspect_classic"
|
self.final_size_auto_height_pal = 576
|
||||||
elif (disc_type == "svcd"):
|
self.final_size_auto_height_ntsc = 480
|
||||||
self.video_rate = 2000
|
self.video_rate_auto_value = -1
|
||||||
self.audio_rate = 224
|
self.audio_rate_auto_value = -1
|
||||||
self.final_size = "size_480x576"
|
elif (disc_type == "cvd"):
|
||||||
self.aspect_ratio = "aspect_classic"
|
self.final_size_auto_width = 352
|
||||||
elif (disc_type == "cvd"):
|
self.final_size_auto_height_pal = 576
|
||||||
self.video_rate = 2000
|
self.final_size_auto_height_ntsc = 480
|
||||||
self.audio_rate = 224
|
self.video_rate_auto_value = -1
|
||||||
self.final_size = "size_352x576"
|
self.audio_rate_auto_value = -1
|
||||||
self.aspect_ratio = "aspect_classic"
|
else: # dvd, divx and mkv
|
||||||
elif (disc_type == "divx"):
|
self.final_size_auto_width = -1
|
||||||
self.video_rate = 5000
|
self.final_size_auto_height_pal = -1
|
||||||
self.audio_rate = 224
|
self.final_size_auto_height_ntsc = -1
|
||||||
self.final_size = "size_auto"
|
self.video_rate_auto_value = -1
|
||||||
self.aspect_ratio = "aspect_auto"
|
self.audio_rate_auto_value = -1
|
||||||
|
|
||||||
|
|
||||||
def delete_file(self):
|
def delete_file(self):
|
||||||
|
|
||||||
print("Deleted file "+self.file_name)
|
print("Deleted file "+self.file_name)
|
||||||
|
|
||||||
def properties(self,model,treeiter):
|
def properties(self):
|
||||||
|
|
||||||
if (self.wfile_properties != None):
|
if (self.wfile_properties != None):
|
||||||
self.wfile_properties.present()
|
self.wfile_properties.present()
|
||||||
return
|
return
|
||||||
|
|
||||||
self.model = model
|
|
||||||
self.treeiter = treeiter
|
|
||||||
|
|
||||||
self.builder = Gtk.Builder()
|
self.builder = Gtk.Builder()
|
||||||
self.builder.set_translation_domain(self.config.gettext_domain)
|
self.builder.set_translation_domain(self.config.gettext_domain)
|
||||||
|
|
||||||
|
|
@ -90,25 +167,89 @@ class file_movie(GObject.GObject):
|
||||||
self.builder.connect_signals(self)
|
self.builder.connect_signals(self)
|
||||||
self.wfile_properties = self.builder.get_object("file_properties")
|
self.wfile_properties = self.builder.get_object("file_properties")
|
||||||
self.wfile_properties.show_all()
|
self.wfile_properties.show_all()
|
||||||
|
|
||||||
self.wfile = self.builder.get_object("label_filename")
|
self.wshow_in_menu = self.builder.get_object("show_in_menu")
|
||||||
self.wtitle = self.builder.get_object("entry_title")
|
|
||||||
|
self.wnotebook = self.builder.get_object("notebook")
|
||||||
self.wfile.set_text(self.file_name)
|
|
||||||
self.wtitle.set_text(self.title_name)
|
# elements in page GENERAL
|
||||||
|
self.wframe_video_rate = self.builder.get_object("frame_video_rate")
|
||||||
|
self.wframe_audio_rate = self.builder.get_object("frame_audio_rate")
|
||||||
|
self.wframe_division_chapters = self.builder.get_object("frame_division_chapters")
|
||||||
|
|
||||||
|
# elements in page VIDEO OPTIONS
|
||||||
|
self.wsize_1920x1080 = self.builder.get_object("size_1920x1080")
|
||||||
|
self.wsize_1280x720 = self.builder.get_object("size_1280x720")
|
||||||
|
self.wsize_1920x1080_ntsc = self.builder.get_object("size_1920x1080_ntsc")
|
||||||
|
self.wsize_1280x720_ntsc = self.builder.get_object("size_1280x720_ntsc")
|
||||||
|
self.wframe_final_size = self.builder.get_object("frame_final_size")
|
||||||
|
self.wframe_aspect_ratio = self.builder.get_object("frame_aspect_ratio")
|
||||||
|
|
||||||
|
# elements in page AUDIO
|
||||||
|
self.wsound5_1 = self.builder.get_object("sound5_1")
|
||||||
|
self.wcopy_sound = self.builder.get_object("copy_sound")
|
||||||
|
|
||||||
|
# Adjust the interface UI to the kind of disc
|
||||||
|
|
||||||
|
if (self.disc_type == 'dvd'):
|
||||||
|
self.wsize_1920x1080.hide()
|
||||||
|
self.wsize_1280x720.hide()
|
||||||
|
self.wsize_1920x1080_ntsc.hide()
|
||||||
|
self.wsize_1280x720_ntsc.hide()
|
||||||
|
elif (self.disc_type == 'vcd'):
|
||||||
|
self.wshow_in_menu.hide()
|
||||||
|
self.wframe_video_rate.hide()
|
||||||
|
self.wframe_audio_rate.hide()
|
||||||
|
self.wframe_division_chapters.hide()
|
||||||
|
self.wframe_final_size.hide()
|
||||||
|
self.wframe_aspect_ratio.hide()
|
||||||
|
self.wsound5_1.hide()
|
||||||
|
self.wcopy_sound.hide()
|
||||||
|
self.wnotebook.remove_page(5)
|
||||||
|
elif (self.disc_type == 'svcd'):
|
||||||
|
self.wsize_1920x1080.hide()
|
||||||
|
self.wsize_1280x720.hide()
|
||||||
|
self.wsize_1920x1080_ntsc.hide()
|
||||||
|
self.wsize_1280x720_ntsc.hide()
|
||||||
|
self.wshow_in_menu.hide()
|
||||||
|
self.wframe_division_chapters.hide()
|
||||||
|
self.wframe_aspect_ratio.hide()
|
||||||
|
self.wsound5_1.hide()
|
||||||
|
self.wcopy_sound.hide()
|
||||||
|
self.wnotebook.remove_page(5)
|
||||||
|
elif (self.disc_type == 'cvd'):
|
||||||
|
self.wsize_1920x1080.hide()
|
||||||
|
self.wsize_1280x720.hide()
|
||||||
|
self.wsize_1920x1080_ntsc.hide()
|
||||||
|
self.wsize_1280x720_ntsc.hide()
|
||||||
|
self.wshow_in_menu.hide()
|
||||||
|
self.wframe_division_chapters.hide()
|
||||||
|
self.wframe_aspect_ratio.hide()
|
||||||
|
self.wsound5_1.hide()
|
||||||
|
self.wcopy_sound.hide()
|
||||||
|
self.wnotebook.remove_page(5)
|
||||||
|
elif (self.disc_type == 'divx'):
|
||||||
|
self.wshow_in_menu.hide()
|
||||||
|
self.wframe_division_chapters.hide()
|
||||||
|
self.wsound5_1.hide()
|
||||||
|
self.wcopy_sound.hide()
|
||||||
|
self.wnotebook.remove_page(5)
|
||||||
|
self.wnotebook.remove_page(1)
|
||||||
|
elif (self.disc_type == 'mkv'):
|
||||||
|
self.wshow_in_menu.hide()
|
||||||
|
self.wframe_division_chapters.hide()
|
||||||
|
self.wnotebook.remove_page(5)
|
||||||
|
|
||||||
|
self.update_ui(self.builder)
|
||||||
|
|
||||||
def on_button_accept_clicked(self,b):
|
def on_button_accept_clicked(self,b):
|
||||||
|
|
||||||
self.title_name = self.wtitle.get_text()
|
self.store_ui(self.builder)
|
||||||
self.model.set_value(self.treeiter,1,self.title_name)
|
self.emit('title_changed',self.title_name)
|
||||||
self.on_button_cancel_clicked(None)
|
self.on_button_cancel_clicked(None)
|
||||||
|
|
||||||
def on_button_cancel_clicked(self,b):
|
def on_button_cancel_clicked(self,b):
|
||||||
|
|
||||||
self.wfile_properties.destroy()
|
self.wfile_properties.destroy()
|
||||||
self.wfile_properties = None
|
self.wfile_properties = None
|
||||||
self.builder = None
|
self.builder = None
|
||||||
self.model = None
|
|
||||||
self.treeiter = None
|
|
||||||
|
|
||||||
|
|
||||||
266
src/devede/interface_manager.py
Normal file
266
src/devede/interface_manager.py
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
# Copyright 2014 (C) Raster Software Vigo (Sergio Costas)
|
||||||
|
#
|
||||||
|
# This file is part of DeVeDe-NG
|
||||||
|
#
|
||||||
|
# DeVeDe-NG is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# DeVeDe-NG 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
from gi.repository import GObject
|
||||||
|
|
||||||
|
class interface_manager(GObject.GObject):
|
||||||
|
""" This class allows to automatically generate variables for a GLADE interface,
|
||||||
|
set the widgets in the interface to their values, and copy the current values
|
||||||
|
in the widgets to the variables """
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.groups = {}
|
||||||
|
self.toggles = []
|
||||||
|
self.labels = []
|
||||||
|
self.text = []
|
||||||
|
self.show_hide = []
|
||||||
|
self.enable_disable = []
|
||||||
|
self.float_adjustments = []
|
||||||
|
self.integer_adjustments = []
|
||||||
|
|
||||||
|
def add_group(self,group_name,radiobutton_list,default_value):
|
||||||
|
""" Adds a group of radiobuttons and creates an internal variable with
|
||||||
|
the name group_name, setting it to default_value. The
|
||||||
|
value for the variable will be the name of the active
|
||||||
|
radiobutton """
|
||||||
|
|
||||||
|
if (default_value != None):
|
||||||
|
exec('self.'+group_name+' = "'+str(default_value)+'"')
|
||||||
|
else:
|
||||||
|
exec('self.'+group_name+' = None')
|
||||||
|
self.groups[group_name] = radiobutton_list
|
||||||
|
|
||||||
|
def add_toggle(self,toggle_name,default_value):
|
||||||
|
""" Adds an internal variable with the name toggle_name, linked to a widget
|
||||||
|
element with the same name (must be or inherint from Gtk.ToogleButton).
|
||||||
|
The default value can be True of False """
|
||||||
|
|
||||||
|
exec('self.'+toggle_name+' = '+str(default_value))
|
||||||
|
self.toggles.append(toggle_name)
|
||||||
|
|
||||||
|
def add_text(self,text_name,default_value):
|
||||||
|
""" Adds an internal variable with the name text_name, linked to an
|
||||||
|
element with the same name (must be a Gtk.TextEntry or a Gtk.Label).
|
||||||
|
The default value can be a text or None """
|
||||||
|
|
||||||
|
if (default_value != None):
|
||||||
|
exec('self.'+text_name+' = "'+str(default_value)+'"')
|
||||||
|
else:
|
||||||
|
exec('self.'+text_name+' = None')
|
||||||
|
self.text.append(text_name)
|
||||||
|
|
||||||
|
def add_label(self,text_name,default_value):
|
||||||
|
""" Adds an internal variable with the name text_name, linked to an
|
||||||
|
element with the same name (must be a Gtk.TextEntry or a Gtk.Label).
|
||||||
|
The default value can be a text or None. This element is copied to the UI,
|
||||||
|
but is never updated from the UI if the user changes it """
|
||||||
|
|
||||||
|
exec('self.'+text_name+' = default_value')
|
||||||
|
self.labels.append(text_name)
|
||||||
|
|
||||||
|
def add_integer_adjustment(self,adjustment_name,default_value):
|
||||||
|
""" Adds an internal variable with the name text_name, linked to an
|
||||||
|
element with the same name (must be a Gtk.Adjustment).
|
||||||
|
The default value must be an integer """
|
||||||
|
|
||||||
|
exec('self.'+adjustment_name+' = '+str(default_value))
|
||||||
|
self.integer_adjustments.append(adjustment_name)
|
||||||
|
|
||||||
|
def add_float_adjustment(self,adjustment_name,default_value):
|
||||||
|
""" Adds an internal variable with the name text_name, linked to an
|
||||||
|
element with the same name (must be a Gtk.Adjustment).
|
||||||
|
The default value must be an float """
|
||||||
|
|
||||||
|
exec('self.'+adjustment_name+' = '+str(default_value))
|
||||||
|
self.float_adjustments.append(adjustment_name)
|
||||||
|
|
||||||
|
|
||||||
|
def add_show_hide(self,element_name,to_show,to_hide):
|
||||||
|
""" Adds an element that can be active or inactive, and two lists of elements.
|
||||||
|
The first one contains elements that will be visible when the element is
|
||||||
|
active, and invisible when it is inactive, and the second one contains
|
||||||
|
elements that will be visible when the element is inactive, and
|
||||||
|
invisible when the element is active """
|
||||||
|
|
||||||
|
self.show_hide.append([element_name, to_show, to_hide])
|
||||||
|
|
||||||
|
def add_enable_disable(self,element_name,to_enable,to_disable):
|
||||||
|
""" Adds an element that can be active or inactive, and two lists of elements.
|
||||||
|
The first one contains elements that will be enabled when the element is
|
||||||
|
active, and disabled when it is inactive, and the second one contains
|
||||||
|
elements that will be enabled when the element is inactive, and
|
||||||
|
disabled when the element is active """
|
||||||
|
|
||||||
|
self.enable_disable.append([element_name, to_enable, to_disable])
|
||||||
|
|
||||||
|
def update_ui(self,builder):
|
||||||
|
""" Sets the value of the widgets in base of the internal variables """
|
||||||
|
|
||||||
|
for key in self.groups:
|
||||||
|
obj = eval('self.'+key)
|
||||||
|
builder.get_object(obj).set_active(True)
|
||||||
|
|
||||||
|
for element in self.toggles:
|
||||||
|
value = eval('self.'+element)
|
||||||
|
builder.get_object(element).set_active(value)
|
||||||
|
|
||||||
|
for element in self.text:
|
||||||
|
value = eval('self.'+element)
|
||||||
|
if (value != None):
|
||||||
|
builder.get_object(element).set_text(value)
|
||||||
|
else:
|
||||||
|
builder.get_object(element).set_text("")
|
||||||
|
|
||||||
|
for element in self.labels:
|
||||||
|
value = eval('self.'+element)
|
||||||
|
if (value != None):
|
||||||
|
builder.get_object(element).set_text(str(value))
|
||||||
|
else:
|
||||||
|
builder.get_object(element).set_text("")
|
||||||
|
|
||||||
|
for element in self.integer_adjustments:
|
||||||
|
value = eval('self.'+element)
|
||||||
|
builder.get_object(element).set_value(float(value))
|
||||||
|
|
||||||
|
for element in self.float_adjustments:
|
||||||
|
value = eval('self.'+element)
|
||||||
|
builder.get_object(element).set_value(value)
|
||||||
|
|
||||||
|
self.show_hide_obj = {}
|
||||||
|
for element in self.show_hide:
|
||||||
|
obj = builder.get_object(element[0])
|
||||||
|
to_show = []
|
||||||
|
for e2 in element[1]:
|
||||||
|
to_show.append(builder.get_object(e2))
|
||||||
|
to_hide = []
|
||||||
|
for e3 in element[2]:
|
||||||
|
to_hide.append(builder.get_object(e3))
|
||||||
|
self.show_hide_obj[obj] = [to_show, to_hide]
|
||||||
|
obj.connect('toggled',self.toggled_element)
|
||||||
|
self.toggled_element(obj)
|
||||||
|
|
||||||
|
self.enable_disable_obj = {}
|
||||||
|
for element in self.enable_disable:
|
||||||
|
obj = builder.get_object(element[0])
|
||||||
|
to_enable = []
|
||||||
|
for e2 in element[1]:
|
||||||
|
to_enable.append(builder.get_object(e2))
|
||||||
|
to_disable = []
|
||||||
|
for e3 in element[2]:
|
||||||
|
to_disable.append(builder.get_object(e3))
|
||||||
|
self.enable_disable_obj[obj] = [to_enable, to_disable]
|
||||||
|
obj.connect('toggled',self.toggled_element2)
|
||||||
|
self.toggled_element2(obj)
|
||||||
|
|
||||||
|
def toggled_element(self,element):
|
||||||
|
""" Wenever an element with 'hide' or 'show' needs is toggled, this callback is called """
|
||||||
|
|
||||||
|
# First, show all items for each possible element
|
||||||
|
for key in self.show_hide_obj:
|
||||||
|
to_show = self.show_hide_obj[key][0]
|
||||||
|
to_hide = self.show_hide_obj[key][1]
|
||||||
|
|
||||||
|
active = key.get_active()
|
||||||
|
|
||||||
|
for item in to_show:
|
||||||
|
if active:
|
||||||
|
item.show()
|
||||||
|
|
||||||
|
for item in to_hide:
|
||||||
|
if not active:
|
||||||
|
item.show()
|
||||||
|
|
||||||
|
# And now, hide all items that must be hiden
|
||||||
|
# This is done this way because this allows to have an item being hiden by
|
||||||
|
# one widget, and being shown by another: in that case, it will be hiden always
|
||||||
|
for key in self.show_hide_obj:
|
||||||
|
to_show = self.show_hide_obj[key][0]
|
||||||
|
to_hide = self.show_hide_obj[key][1]
|
||||||
|
|
||||||
|
active = key.get_active()
|
||||||
|
|
||||||
|
for item in to_show:
|
||||||
|
if not active:
|
||||||
|
item.hide()
|
||||||
|
|
||||||
|
for item in to_hide:
|
||||||
|
if active:
|
||||||
|
item.hide()
|
||||||
|
|
||||||
|
|
||||||
|
def toggled_element2(self,element):
|
||||||
|
""" Wenever an element with 'enable' or 'disable' needs is toggled, this callback is called """
|
||||||
|
|
||||||
|
# First enable all items that must be enabled
|
||||||
|
for key in self.enable_disable_obj:
|
||||||
|
to_enable = self.enable_disable_obj[key][0]
|
||||||
|
to_disable = self.enable_disable_obj[key][1]
|
||||||
|
|
||||||
|
active = key.get_active()
|
||||||
|
if (active):
|
||||||
|
for item in to_enable:
|
||||||
|
item.set_sensitive(True)
|
||||||
|
else:
|
||||||
|
for item in to_disable:
|
||||||
|
item.set_sensitive(True)
|
||||||
|
|
||||||
|
# And now, disable all items that must be disabled
|
||||||
|
# This is done this way because this allows to have an item being disabled by
|
||||||
|
# one widget, and being enabled by another: in that case, it will be disabled always
|
||||||
|
for key in self.enable_disable_obj:
|
||||||
|
to_enable = self.enable_disable_obj[key][0]
|
||||||
|
to_disable = self.enable_disable_obj[key][1]
|
||||||
|
|
||||||
|
active = key.get_active()
|
||||||
|
if (not active):
|
||||||
|
for item in to_enable:
|
||||||
|
item.set_sensitive(False)
|
||||||
|
else:
|
||||||
|
for item in to_disable:
|
||||||
|
item.set_sensitive(False)
|
||||||
|
|
||||||
|
def store_ui(self,builder):
|
||||||
|
""" Takes the values of the widgets and stores them in the internal variables """
|
||||||
|
|
||||||
|
for key in self.groups:
|
||||||
|
for element in self.groups[key]:
|
||||||
|
obj = builder.get_object(element)
|
||||||
|
if obj.get_active():
|
||||||
|
exec('self.'+key+' = "'+element+'"')
|
||||||
|
break
|
||||||
|
|
||||||
|
for element in self.toggles:
|
||||||
|
obj = builder.get_object(element)
|
||||||
|
if obj.get_active():
|
||||||
|
exec('self.'+element+' = True')
|
||||||
|
else:
|
||||||
|
exec('self.'+element+' = False')
|
||||||
|
|
||||||
|
for element in self.text:
|
||||||
|
obj = builder.get_object(element)
|
||||||
|
exec('self.'+element+' = obj.get_text()')
|
||||||
|
|
||||||
|
for element in self.integer_adjustments:
|
||||||
|
obj = builder.get_object(element)
|
||||||
|
exec('self.'+element+' = int(obj.get_value())')
|
||||||
|
|
||||||
|
for element in self.float_adjustments:
|
||||||
|
obj = builder.get_object(element)
|
||||||
|
exec('self.'+element+' = obj.get_value()')
|
||||||
|
|
||||||
|
|
||||||
105
src/devede/mpv_converter.py
Normal file
105
src/devede/mpv_converter.py
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
# Copyright 2014 (C) Raster Software Vigo (Sergio Costas)
|
||||||
|
#
|
||||||
|
# This file is part of DeVeDe-NG
|
||||||
|
#
|
||||||
|
# DeVeDe-NG is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# DeVeDe-NG 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import devede.configuration_data
|
||||||
|
|
||||||
|
class mpv_converter:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.config = devede.configuration_data.configuration.get_config()
|
||||||
|
self.supports_analize = True
|
||||||
|
self.supports_play = True
|
||||||
|
self.supports_convert = False
|
||||||
|
self.supports_menu = False
|
||||||
|
self.display_name = "MPV"
|
||||||
|
|
||||||
|
def check_is_installed(self):
|
||||||
|
handle = subprocess.Popen(["mpv","-v"])
|
||||||
|
if 0==handle.wait():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_film_data(self,movie):
|
||||||
|
""" processes a file, refered by the FILE_MOVIE movie object, and fills its
|
||||||
|
main data (resolution, FPS, length...) """
|
||||||
|
|
||||||
|
def get_film_data2(self,movie,check_audio):
|
||||||
|
|
||||||
|
if (check_audio):
|
||||||
|
frames = "0"
|
||||||
|
else:
|
||||||
|
frames = "1"
|
||||||
|
|
||||||
|
if self.config.mpv_available:
|
||||||
|
command_line = ["mpv","identify","-loop","1","-identify", "-ao", "null", "-vo", "null", "-frames", "1"]
|
||||||
|
else:
|
||||||
|
command_line = ["mplayer","identify","-loop","1","-identify", "-ao", "null", "-vo", "null", "-frames", "1"]
|
||||||
|
|
||||||
|
command_line.append(movie.file_name)
|
||||||
|
|
||||||
|
handle = subprocess.Popen(command_line)
|
||||||
|
(stdout, stderr) = handle.communicate()
|
||||||
|
|
||||||
|
minimum_audio=-1
|
||||||
|
audio_list=[]
|
||||||
|
audio = 0
|
||||||
|
video = 0
|
||||||
|
|
||||||
|
for line in stdout:
|
||||||
|
line=self.remove_ansi(line)
|
||||||
|
if line == "":
|
||||||
|
continue
|
||||||
|
position=line.find("ID_")
|
||||||
|
if position==-1:
|
||||||
|
continue
|
||||||
|
line=line[position:]
|
||||||
|
if line[:16]=="ID_VIDEO_BITRATE":
|
||||||
|
vrate=int(line[17:])
|
||||||
|
if line[:14]=="ID_VIDEO_WIDTH":
|
||||||
|
width=int(line[15:])
|
||||||
|
if line[:15]=="ID_VIDEO_HEIGHT":
|
||||||
|
height=int(line[16:])
|
||||||
|
if line[:15]=="ID_VIDEO_ASPECT":
|
||||||
|
aspect_ratio=float(line[16:])
|
||||||
|
if line[:12]=="ID_VIDEO_FPS":
|
||||||
|
fps2=line[13:]
|
||||||
|
while ord(fps2[-1])<32:
|
||||||
|
fps2=fps2[:-1]
|
||||||
|
posic=line.find(".")
|
||||||
|
if posic==-1:
|
||||||
|
fps=int(line[13:])
|
||||||
|
else:
|
||||||
|
fps=int(line[13:posic])
|
||||||
|
if line[posic+1]=="9":
|
||||||
|
fps+=1
|
||||||
|
if line[:16]=="ID_AUDIO_BITRATE":
|
||||||
|
arate=int(line[17:])
|
||||||
|
if line[:13]=="ID_AUDIO_RATE":
|
||||||
|
audiorate=int(line[14:])
|
||||||
|
if line[:9]=="ID_LENGTH":
|
||||||
|
length=int(float(line[10:]))
|
||||||
|
if line[:11]=="ID_VIDEO_ID":
|
||||||
|
video+=1
|
||||||
|
if line[:11]=="ID_AUDIO_ID":
|
||||||
|
audio+=1
|
||||||
|
audio_track=int(line[12:])
|
||||||
|
if (minimum_audio == -1) or (minimum_audio>audio_track):
|
||||||
|
minimum_audio=audio_track
|
||||||
|
audio_list.append(audio_track)
|
||||||
|
|
@ -27,83 +27,62 @@ class devede_project:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.config = devede.configuration_data.configuration.get_config()
|
self.config = devede.configuration_data.configuration.get_config()
|
||||||
self.destroy_all()
|
|
||||||
|
|
||||||
|
self.disc_type = self.config.disc_type
|
||||||
|
|
||||||
def destroy_all(self):
|
self.current_title = None
|
||||||
|
|
||||||
self.wask_window = None
|
|
||||||
self.wmain_window = None
|
|
||||||
self.wdisc_size = None
|
|
||||||
self.wliststore_files = None
|
|
||||||
self.wfiles = None
|
|
||||||
self.wdisc_fill_level = None
|
|
||||||
self.wuse_pal = None
|
|
||||||
self.wuse_ntsc = None
|
|
||||||
self.wcreate_menu = None
|
|
||||||
self.disc_type = None
|
|
||||||
self.wadd_file = None
|
|
||||||
self.wdelete_file = None
|
|
||||||
self.wup_file = None
|
|
||||||
self.wdown_file = None
|
|
||||||
self.wproperties_file = None
|
|
||||||
self.wcreate_disc = None
|
|
||||||
|
|
||||||
|
|
||||||
def ask_type(self):
|
|
||||||
""" This method resets the project and asks the user what kind of project want to do """
|
|
||||||
|
|
||||||
if (self.wask_window != None):
|
|
||||||
self.wask_window.present()
|
|
||||||
return
|
|
||||||
|
|
||||||
builder = Gtk.Builder()
|
builder = Gtk.Builder()
|
||||||
builder.set_translation_domain(self.config.gettext_domain)
|
builder.set_translation_domain(self.config.gettext_domain)
|
||||||
|
|
||||||
builder.add_from_file(os.path.join(self.config.glade,"wselect_disk.ui"))
|
builder.add_from_file(os.path.join(self.config.glade,"wmain.ui"))
|
||||||
builder.connect_signals(self)
|
builder.connect_signals(self)
|
||||||
self.wask_window = builder.get_object("wselect_disk")
|
|
||||||
self.wask_window.show_all()
|
|
||||||
|
|
||||||
def on_wselect_disk_delete_event(self,w,e):
|
# Interface widgets
|
||||||
Gtk.main_quit()
|
self.wmain_window = builder.get_object("wmain_window")
|
||||||
return False
|
self.wdisc_size = builder.get_object("disc_size")
|
||||||
|
self.wliststore_files = builder.get_object("liststore_files")
|
||||||
|
self.wfiles = builder.get_object("files")
|
||||||
|
self.wdisc_fill_level = builder.get_object("disc_fill_level")
|
||||||
|
self.wuse_pal = builder.get_object("use_pal")
|
||||||
|
self.wuse_ntsc = builder.get_object("use_ntsc")
|
||||||
|
self.wcreate_menu = builder.get_object("create_menu")
|
||||||
|
self.wframe_titles = builder.get_object("frame_titles")
|
||||||
|
|
||||||
def on_button_dvd_clicked(self,b):
|
self.wadd_file = builder.get_object("add_file")
|
||||||
|
self.wdelete_file = builder.get_object("delete_file")
|
||||||
|
self.wup_file = builder.get_object("up_file")
|
||||||
|
self.wdown_file = builder.get_object("down_file")
|
||||||
|
self.wproperties_file = builder.get_object("properties_file")
|
||||||
|
self.wpreview_file = builder.get_object("preview_file")
|
||||||
|
|
||||||
self.set_type("dvd")
|
self.wcreate_disc = builder.get_object("create_disc")
|
||||||
|
|
||||||
def on_button_vcd_clicked(self,b):
|
selection = self.wfiles.get_selection()
|
||||||
|
selection.mode = Gtk.SelectionMode.SINGLE
|
||||||
|
|
||||||
self.set_type("vcd")
|
if (self.config.PAL):
|
||||||
|
self.wuse_pal.set_active(True)
|
||||||
|
else:
|
||||||
|
self.wuse_ntsc.set_active(True)
|
||||||
|
|
||||||
def on_button_svcd_clicked(self,b):
|
self.config.connect('disc_type',self.set_type)
|
||||||
|
if (self.disc_type != None):
|
||||||
self.set_type("svcd")
|
self.set_type(None, self.disc_type)
|
||||||
|
|
||||||
def on_button_cvd_clicked(self,b):
|
|
||||||
|
|
||||||
self.set_type("cvd")
|
|
||||||
|
|
||||||
def on_button_divx_clicked(self,b):
|
|
||||||
|
|
||||||
self.set_type("divx")
|
|
||||||
|
|
||||||
|
|
||||||
def set_type(self,disc_type):
|
def set_type(self,obj=None,disc_type=None):
|
||||||
""" this method sets the disk type to the specified one and adapts the interface
|
""" this method sets the disk type to the specified one and adapts the interface
|
||||||
in the main window to it. Also leaves everything ready to start creating a
|
in the main window to it. Also leaves everything ready to start creating a
|
||||||
new disc """
|
new disc """
|
||||||
|
|
||||||
if (self.wask_window != None):
|
if (disc_type != None):
|
||||||
self.wask_window.destroy()
|
self.disc_type = disc_type
|
||||||
self.destroy_all()
|
self.wmain_window.show_all()
|
||||||
|
self.set_interface_status(None)
|
||||||
self.disc_type = disc_type
|
|
||||||
self.show_main_window()
|
|
||||||
|
|
||||||
# Set the default disc size
|
# Set the default disc size
|
||||||
if (self.disc_type == "dvd"):
|
if (self.disc_type == "dvd") or (self.disc_type == "mkv"):
|
||||||
self.wdisc_size.set_active(1) # 4.7 GB DVD
|
self.wdisc_size.set_active(1) # 4.7 GB DVD
|
||||||
else:
|
else:
|
||||||
self.wdisc_size.set_active(3) # 700 MB CD
|
self.wdisc_size.set_active(3) # 700 MB CD
|
||||||
|
|
@ -156,52 +135,8 @@ class devede_project:
|
||||||
self.wdown_file.set_sensitive(False)
|
self.wdown_file.set_sensitive(False)
|
||||||
|
|
||||||
|
|
||||||
def show_main_window(self):
|
|
||||||
|
|
||||||
if (self.wmain_window != None):
|
|
||||||
self.wmain_window.present()
|
|
||||||
return
|
|
||||||
|
|
||||||
self.current_title = None
|
|
||||||
|
|
||||||
builder = Gtk.Builder()
|
|
||||||
builder.set_translation_domain(self.config.gettext_domain)
|
|
||||||
|
|
||||||
builder.add_from_file(os.path.join(self.config.glade,"wmain.ui"))
|
|
||||||
builder.connect_signals(self)
|
|
||||||
|
|
||||||
# Interface widgets
|
|
||||||
self.wmain_window = builder.get_object("wmain_window")
|
|
||||||
self.wdisc_size = builder.get_object("disc_size")
|
|
||||||
self.wliststore_files = builder.get_object("liststore_files")
|
|
||||||
self.wfiles = builder.get_object("files")
|
|
||||||
self.wdisc_fill_level = builder.get_object("disc_fill_level")
|
|
||||||
self.wuse_pal = builder.get_object("use_pal")
|
|
||||||
self.wuse_ntsc = builder.get_object("use_ntsc")
|
|
||||||
self.wcreate_menu = builder.get_object("create_menu")
|
|
||||||
self.wframe_titles = builder.get_object("frame_titles")
|
|
||||||
|
|
||||||
self.wadd_file = builder.get_object("add_file")
|
|
||||||
self.wdelete_file = builder.get_object("delete_file")
|
|
||||||
self.wup_file = builder.get_object("up_file")
|
|
||||||
self.wdown_file = builder.get_object("down_file")
|
|
||||||
self.wproperties_file = builder.get_object("properties_file")
|
|
||||||
self.wpreview_file = builder.get_object("preview_file")
|
|
||||||
|
|
||||||
self.wcreate_disc = builder.get_object("create_disc")
|
|
||||||
|
|
||||||
selection = self.wfiles.get_selection()
|
|
||||||
selection.mode = Gtk.SelectionMode.SINGLE
|
|
||||||
|
|
||||||
if (self.config.PAL):
|
|
||||||
self.wuse_pal.set_active(True)
|
|
||||||
else:
|
|
||||||
self.wuse_ntsc.set_active(True)
|
|
||||||
self.wmain_window.show_all()
|
|
||||||
self.set_interface_status(None)
|
|
||||||
|
|
||||||
def on_use_pal_toggled(self,b):
|
def on_use_pal_toggled(self,b):
|
||||||
|
|
||||||
self.config.PAL = self.wuse_pal.get_active()
|
self.config.PAL = self.wuse_pal.get_active()
|
||||||
|
|
||||||
def on_wmain_window_delete_event(self,b,e=None):
|
def on_wmain_window_delete_event(self,b,e=None):
|
||||||
|
|
@ -211,14 +146,21 @@ class devede_project:
|
||||||
Gtk.main_quit()
|
Gtk.main_quit()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def title_changed(self,obj,new_title):
|
||||||
|
|
||||||
|
for item in self.wliststore_files:
|
||||||
|
element = item.model[item.iter][0]
|
||||||
|
if element == obj:
|
||||||
|
item.model.set_value(item.iter,1,new_title)
|
||||||
|
|
||||||
|
|
||||||
def on_add_file_clicked(self,b):
|
def on_add_file_clicked(self,b):
|
||||||
|
|
||||||
ask_files = devede.add_files.add_files()
|
ask_files = devede.add_files.add_files()
|
||||||
ask_files.set_type(self.disc_type)
|
|
||||||
if (ask_files.run()):
|
if (ask_files.run()):
|
||||||
for efile in ask_files.files:
|
for efile in ask_files.files:
|
||||||
new_file = devede.file_movie.file_movie(efile)
|
new_file = devede.file_movie.file_movie(efile)
|
||||||
new_file.set_type(self.disc_type)
|
new_file.connect('title_changed',self.title_changed)
|
||||||
self.wliststore_files.append([new_file, new_file.title_name])
|
self.wliststore_files.append([new_file, new_file.title_name])
|
||||||
self.set_interface_status(None)
|
self.set_interface_status(None)
|
||||||
|
|
||||||
|
|
@ -259,8 +201,7 @@ class devede_project:
|
||||||
(element, position, model, treeiter) = self.get_current_file()
|
(element, position, model, treeiter) = self.get_current_file()
|
||||||
if (element == None):
|
if (element == None):
|
||||||
return
|
return
|
||||||
|
element.properties()
|
||||||
element.properties(model,treeiter)
|
|
||||||
|
|
||||||
def on_adjust_disc_usage_clicked(self,b):
|
def on_adjust_disc_usage_clicked(self,b):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,11 @@ from gi.repository import Gtk
|
||||||
|
|
||||||
import devede.project
|
import devede.project
|
||||||
import devede.configuration_data
|
import devede.configuration_data
|
||||||
|
import devede.choose_disc_type
|
||||||
|
|
||||||
config_data = devede.configuration_data.configuration.get_config()
|
config_data = devede.configuration_data.configuration.get_config()
|
||||||
|
|
||||||
if config_data.error:
|
if config_data == None:
|
||||||
print ("Can't locate extra files. Aborting.")
|
print ("Can't locate extra files. Aborting.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
@ -44,6 +45,6 @@ _ = gettext.gettext
|
||||||
Gtk.init(sys.argv)
|
Gtk.init(sys.argv)
|
||||||
|
|
||||||
mwindow = devede.project.devede_project()
|
mwindow = devede.project.devede_project()
|
||||||
mwindow.ask_type()
|
ask_type = devede.choose_disc_type.choose_disc_type()
|
||||||
Gtk.main()
|
Gtk.main()
|
||||||
config_data.save_config()
|
config_data.save_config()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue