# 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
import cairo
from gi.repository import Gtk, Gdk, GdkPixbuf
import os
import devedeng.configuration_data
import devedeng.interface_manager
import devedeng.message
import devedeng.mux_dvd_menu
class dvd_menu(devedeng.interface_manager.interface_manager):
def __init__(self):
self.entry_vertical_margin = 2.0
self.entry_separation = 2.0
devedeng.interface_manager.interface_manager.__init__(self)
self.config = devedeng.configuration_data.configuration.get_config()
self.default_background = os.path.join(
self.config.pic_path, "backgrounds", "default_bg.png")
self.default_sound = os.path.join(self.config.pic_path, "silence.ogg")
self.add_colorbutton("title_color", (0, 0, 0, 1), self.update_preview)
self.add_colorbutton("title_shadow", (0, 0, 0, 0), self.update_preview)
self.add_colorbutton("unselected_color",
(1, 1, 1, 1), self.update_preview)
self.add_colorbutton("shadow_color", (0, 0, 0, 0), self.update_preview)
self.add_colorbutton(
"selected_color", (0, 1, 1, 1), self.update_preview)
self.add_colorbutton("background_color",
(0, 0, 0, 0.75), self.update_preview)
self.add_text("title_text", None, self.update_preview)
self.add_text("playall_text", _("Play all"), self.update_preview)
self.add_group("position_horizontal", [
"left", "center", "right"], "center", self.update_preview)
self.add_group("at_startup", [
"menu_show_at_startup", "play_first_title_at_startup"], "menu_show_at_startup")
self.add_toggle("play_all_c", False, self.update_preview)
self.add_integer_adjustment("sound_length", 30)
self.add_dualtoggle("audio_mp2", "audio_ac3", True)
self.add_float_adjustment("margin_left", 10.0, self.update_preview)
self.add_float_adjustment("margin_top", 12.5, self.update_preview)
self.add_float_adjustment("margin_right", 10.0, self.update_preview)
self.add_float_adjustment("margin_bottom", 12.5, self.update_preview)
self.add_float_adjustment("title_horizontal", 0.0, self.update_preview)
self.add_float_adjustment("title_vertical", 10.0, self.update_preview)
self.add_fontbutton("title_font", "Sans 28", self.update_preview)
self.add_fontbutton("entry_font", "Sans 28", self.update_preview)
self.add_filebutton("background_picture",
self.default_background, self.update_preview)
self.add_filebutton("background_music",
self.default_sound, self.update_music)
self.cached_menu_font = None
self.cached_menu_size = 0
self.video_rate = 2500
self.audio_rate = 224
def on_help_clicked(self, b):
help_file = devedeng.help.help("menu.html")
def get_estimated_size(self):
estimated_size = ((self.video_rate + self.audio_rate)
* self.sound_length) / 8
return estimated_size
def update_music(self, b=None):
self.store_ui(self.builder)
cv = devedeng.converter.converter.get_converter()
film_analizer = (cv.get_film_analizer())()
film_analizer.get_film_data(self.background_music)
if (film_analizer.video_streams != 0):
devedeng.message.message_window(_("The selected file is a video, not an audio file"), _("Error"))
self.on_no_sound_clicked(None)
elif (film_analizer.audio_streams == 0):
devedeng.message.message_window(_("The selected file is not an audio file"), _("Error"))
self.on_no_sound_clicked(None)
else:
self.sound_length = film_analizer.original_length
def update_preview(self, b=None):
self.store_ui(self.builder)
self.sf = None # force to repaint the menu
self.wdrawing_area.queue_draw()
def on_default_background_clicked(self, b):
self.background_picture = self.default_background
self.update_ui(self.builder)
self.update_preview()
def on_no_sound_clicked(self, b):
self.background_music = self.default_sound
self.sound_length = 30
self.update_ui(self.builder)
def show_configuration(self, file_list):
self.builder = Gtk.Builder()
self.builder.set_translation_domain(self.config.gettext_domain)
self.file_list = file_list
self.refresh_static_data()
self.builder.add_from_file(os.path.join(self.config.glade, "wmenu.ui"))
self.builder.connect_signals(self)
self.wframe_preview = self.builder.get_object("frame_preview")
self.wframe_preview_controls = self.builder.get_object(
"frame_preview_controls")
self.wmenu = self.builder.get_object("menu")
self.wpreview_menu = self.builder.get_object("preview_menu")
self.wdrawing_area = self.builder.get_object("drawingarea_preview")
self.wcurrent_page = self.builder.get_object("current_page")
self.wshow_as_selected = self.builder.get_object("show_as_selected")
self.wshow_as_selected.connect("toggled", self.update_preview)
self.wbackground_picture = self.builder.get_object(
"background_picture")
self.wbackground_music = self.builder.get_object("background_music")
file_filter_pictures = Gtk.FileFilter()
file_filter_pictures.set_name(_("Picture files"))
file_filter_pictures.add_mime_type("image/*")
file_filter_all = Gtk.FileFilter()
file_filter_all.set_name(_("All files"))
file_filter_all.add_pattern("*")
self.wbackground_picture.add_filter(file_filter_pictures)
self.wbackground_picture.add_filter(file_filter_all)
file_filter_music = Gtk.FileFilter()
file_filter_music.set_name(_("Sound files"))
file_filter_music.add_mime_type("audio/*")
file_filter_all = Gtk.FileFilter()
file_filter_all.set_name(_("All files"))
file_filter_all.add_pattern("*")
self.wbackground_music.add_filter(file_filter_music)
self.wbackground_music.add_filter(file_filter_all)
self.wmenu.show_all()
self.pages = 0
self.update_ui(self.builder)
self.save_ui()
def on_accept_clicked(self, b):
self.store_ui(self.builder)
self.wmenu.destroy()
def on_cancel_clicked(self, b):
self.restore_ui()
self.wmenu.destroy()
def get_font_params(self, font_name):
font_elements = font_name.split(" ")
if (len(font_elements)) < 2:
fontname = "Sans"
fontstyle = cairo.FONT_WEIGHT_NORMAL
fontslant = cairo.FONT_SLANT_NORMAL
fontsize = 12
else:
fontname = ""
fontstyle = cairo.FONT_WEIGHT_NORMAL
fontslant = cairo.FONT_SLANT_NORMAL
for counter2 in range(len(font_elements) - 1):
if font_elements[counter2] == "Bold":
fontstyle = cairo.FONT_WEIGHT_BOLD
elif font_elements[counter2] == "Italic":
fontslant = cairo.FONT_SLANT_ITALIC
else:
fontname += " " + font_elements[counter2]
if fontname != "":
fontname = fontname[1:]
else:
fontname = "Sans"
try:
fontsize = float(font_elements[-1])
except:
fontsize = 12
return fontname, fontstyle, fontslant, fontsize
def paint_arrow(self, xl, xr, y, arrow_type, left):
if arrow_type == "menu_entry":
fgcolor = self.unselected_color
elif arrow_type == "menu_entry_selected":
fgcolor = self.selected_color
elif arrow_type == "menu_entry_activated":
fgcolor = (1.0 - self.selected_color[0], 1.0 - self.selected_color[
1], 1.0 - self.selected_color[2], self.selected_color[3])
else:
return
fo = cairo.FontOptions()
fo.set_antialias(cairo.ANTIALIAS_NONE)
self.cr.set_font_options(fo)
self.cr.set_antialias(cairo.ANTIALIAS_NONE)
x = (xl + xr) / 2.0
h = (self.cached_menu_size / 2.0) - 1.0
self.cr.set_source_rgba(fgcolor[0], fgcolor[1], fgcolor[2], fgcolor[3])
self.cr.move_to(x, y - h)
if (left):
self.cr.line_to(x - self.cached_menu_size + 2, y)
else:
self.cr.line_to(x + self.cached_menu_size - 2, y)
self.cr.line_to(x, y + h)
self.cr.line_to(x, y - h)
self.cr.fill()
fo = cairo.FontOptions()
fo.set_antialias(cairo.ANTIALIAS_DEFAULT)
self.cr.set_font_options(fo)
self.cr.set_antialias(cairo.ANTIALIAS_DEFAULT)
def write_text(self, text, text_type, xl, xr, y, alignment):
""" Renders a line of text, in the rectangle delimited by xl,y-h;xr,y+h, with the specified alignment """
if text is None:
return
if text_type == "title":
fgcolor = self.title_color
bgcolor = self.title_shadow
hard_borders = False
font = self.title_font
elif text_type == "menu_entry":
fgcolor = self.unselected_color
bgcolor = self.shadow_color
hard_borders = True
font = self.entry_font
elif text_type == "menu_entry_selected":
fgcolor = self.selected_color
bgcolor = None
hard_borders = True
font = self.entry_font
elif text_type == "menu_entry_activated":
fgcolor = (1.0 - self.selected_color[0], 1.0 - self.selected_color[
1], 1.0 - self.selected_color[2], self.selected_color[3])
bgcolor = None
hard_borders = True
font = self.entry_font
else:
return
if hard_borders:
fo = cairo.FontOptions()
fo.set_antialias(cairo.ANTIALIAS_NONE)
self.cr.set_font_options(fo)
self.cr.set_antialias(cairo.ANTIALIAS_NONE)
(fontname, fontstyle, fontslant, fontsize) = self.get_font_params(font)
self.cr.select_font_face(fontname, fontslant, fontstyle)
self.cr.set_font_size(fontsize)
extents = self.cr.text_extents(text)
if alignment == "left":
x = xl
elif alignment == "right":
x = xr - extents[2]
elif alignment == "center":
x = ((xl + xr) / 2.0) - (extents[2] / 2.0)
if (bgcolor is not None):
self.cr.move_to(x + extents[0] + 2, y -
(extents[3] / 2.0) - extents[1] + 2)
self.cr.set_source_rgba(
bgcolor[0], bgcolor[1], bgcolor[2], bgcolor[3])
self.cr.show_text(text)
self.cr.move_to(x + extents[0], y - (extents[3] / 2.0) - extents[1])
self.cr.set_source_rgba(fgcolor[0], fgcolor[1], fgcolor[2], fgcolor[3])
self.cr.show_text(text)
if hard_borders:
fo = cairo.FontOptions()
fo.set_antialias(cairo.ANTIALIAS_DEFAULT)
self.cr.set_font_options(fo)
self.cr.set_antialias(cairo.ANTIALIAS_DEFAULT)
def refresh_static_data(self):
""" This method sets data that can't be changed from the dvd settings window.
It must be called before painting a menu """
if self.config.PAL:
self.y = 576.0
else:
self.y = 480.0
self.title_list = []
counter = 0
for element in self.file_list:
if element.show_in_menu:
self.title_list.append((element, counter))
counter += 1
self.sf = None
self.current_shown_page = 0
self.wcurrent_page = None
def paint_menu(self, paint_background, paint_selected, paint_activated, page_number):
coordinates = []
if self.sf is None:
self.sf = cairo.ImageSurface(cairo.FORMAT_ARGB32, 720, int(self.y))
self.cr = cairo.Context(self.sf)
if self.cached_menu_font != self.entry_font:
# memorize the font sizes
(fontname, fontstyle, fontslant,
fontsize) = self.get_font_params(self.entry_font)
self.cr.select_font_face(fontname, fontslant, fontstyle)
self.cr.set_font_size(fontsize)
extents = self.cr.font_extents()
self.cached_menu_size = extents[2]
self.cached_menu_font = self.entry_font
if paint_background:
extra_pixbuf = GdkPixbuf.Pixbuf.new_from_file(
self.background_picture)
extra_x = float(extra_pixbuf.get_width())
extra_y = float(extra_pixbuf.get_height())
self.cr.save()
self.cr.scale(720.0 / extra_x, self.y / extra_y)
surface = Gdk.cairo_surface_create_from_pixbuf(
extra_pixbuf, 1, None)
self.cr.set_source_surface(surface, 0, 0)
self.cr.paint()
self.cr.restore()
hmargin = self.title_horizontal * 720.0 / 100.0
self.write_text(self.title_text, "title", 0 + hmargin, 720 +
hmargin, self.title_vertical * self.y / 100.0, "center")
# else:
# self.cr.set_source_rgb(1.0,1.0,1.0)
# self.cr.paint()
top_margin_p = self.y * self.margin_top / 100.0
bottom_margin_p = self.y * self.margin_bottom / 100.0
left_margin_p = 720.0 * self.margin_left / 100.0
right_margin_p = 720.0 * self.margin_right / 100.0
entry_height = self.cached_menu_size + \
self.entry_vertical_margin * 2.0 + self.entry_separation
entries_per_page = int(
(self.y - top_margin_p - bottom_margin_p) / entry_height)
if (self.play_all_c):
entries_per_page -= 1
n_entries = len(self.title_list)
if (n_entries > entries_per_page):
paint_arrows = True
entries_per_page -= 1
else:
paint_arrows = False
self.pages = int(n_entries / entries_per_page)
if (n_entries == 0) or ((n_entries % entries_per_page) != 0):
self.pages += 1
if (page_number >= self.pages) and (page_number > 0):
page_number -= 1
if self.wcurrent_page is not None:
self.wcurrent_page.set_text(_("Page %(X)d of %(Y)d") % {
"X": page_number + 1, "Y": self.pages})
xl = left_margin_p
xr = 720.0 - right_margin_p
y = top_margin_p + entry_height / 2.0
height = (self.cached_menu_size + self.entry_vertical_margin) / 2.0
if (self.play_all_c):
coordinates.append([xl, y - height, xr, y + height, "play_all"])
if self.playall_text == "":
playalltext = "Play all"
else:
playalltext = self.playall_text
if paint_background:
self.paint_base(xl, xr, y, 0)
self.write_text(playalltext, "menu_entry", xl,
xr, y, self.position_horizontal)
if paint_selected:
self.write_text(playalltext, "menu_entry_selected",
xl, xr, y, self.position_horizontal)
if paint_activated:
self.write_text(playalltext, "menu_entry_activated",
xl, xr, y, self.position_horizontal)
y += entry_height
for entry in self.title_list[page_number * entries_per_page:(page_number + 1) * entries_per_page]:
coordinates.append([xl, y - height, xr, y + height, "entry"])
text = entry[0].title_name
if paint_background:
self.paint_base(xl, xr, y, 0)
self.write_text(text, "menu_entry", xl, xr,
y, self.position_horizontal)
if paint_selected:
self.write_text(text, "menu_entry_selected", xl,
xr, y, self.position_horizontal)
if paint_activated:
self.write_text(text, "menu_entry_activated",
xl, xr, y, self.position_horizontal)
y += entry_height
if paint_arrows:
if page_number == 0:
coordinates.append([xl, y - height, xr, y + height, "right"])
if paint_background:
self.paint_base(xl, xr, y, 0)
self.paint_arrow(xl, xr, y, "menu_entry", False)
if paint_selected:
self.paint_arrow(xl, xr, y, "menu_entry_selected", False)
if paint_activated:
self.paint_arrow(xl, xr, y, "menu_entry_activated", False)
elif page_number == (self.pages - 1):
coordinates.append([xl, y - height, xr, y + height, "left"])
if paint_background:
self.paint_base(xl, xr, y, 0)
self.paint_arrow(xl, xr, y, "menu_entry", True)
if paint_selected:
self.paint_arrow(xl, xr, y, "menu_entry_selected", True)
if paint_activated:
self.paint_arrow(xl, xr, y, "menu_entry_activated", True)
else:
med = (xl + xr) / 2.0
coordinates.append([xl, y - height, med, y + height, "left"])
coordinates.append([med, y - height, xr, y + height, "right"])
if paint_background:
self.paint_base(xl, xr, y, 1)
self.paint_base(xl, xr, y, 2)
self.paint_arrow(med, xr, y, "menu_entry", False)
self.paint_arrow(xl, med, y, "menu_entry", True)
if paint_selected:
self.paint_arrow(med, xr, y, "menu_entry_selected", False)
self.paint_arrow(xl, med, y, "menu_entry_selected", True)
if paint_activated:
self.paint_arrow(med, xr, y, "menu_entry_activated", False)
self.paint_arrow(xl, med, y, "menu_entry_activated", True)
return coordinates
def paint_base(self, xl, xr, y, type_b):
height = self.cached_menu_size + self.entry_vertical_margin
radius = (height) / 2.0
y -= radius
if (type_b == 1): # half button, at left
xr = (xl + xr) / 2.0
xr -= radius
elif (type_b == 2): # half button, at right
xl = (xl + xr) / 2.0
xl += radius
self.cr.set_source_rgba(self.background_color[0], self.background_color[
1], self.background_color[2], self.background_color[3])
self.cr.move_to(xl, y)
self.cr.line_to(xr, y)
self.cr.curve_to(xr + radius, y, xr + radius,
y + height, xr, y + height)
self.cr.line_to(xl, y + height)
self.cr.curve_to(xl - radius, y + height, xl - radius, y, xl, y)
self.cr.fill()
def on_next_page_clicked(self, b):
self.current_shown_page += 1
if (self.current_shown_page == self.pages):
self.current_shown_page -= 1
self.update_preview()
def on_previous_page_clicked(self, b):
self.current_shown_page -= 1
if (self.current_shown_page < 0):
self.current_shown_page = 0
self.update_preview()
def on_drawingarea_preview_draw(self, widget, cr):
""" Callback to repaint the menu preview window when it
sends the EXPOSE event """
if (self.sf is None):
self.paint_menu(True, self.wshow_as_selected.get_active(
), False, self.current_shown_page)
if (self.current_shown_page >= self.pages):
self.current_shown_page = self.pages
cr.set_source_surface(self.sf)
cr.paint()
def create_menu_stream(self, path, n_page, coordinates):
""" Creates the menu XML file """
file_name = os.path.join(path, "menu_" + str(n_page) + ".xml")
entry_data = {}
entry_data["chapters"] = []
entry_data["right"] = None
entry_data["left"] = None
xml_file = open(file_name, "w")
# transparent="000000"')
xml_file.write(
'\n\t\n\t\t\n')
n_elements = 0
has_next = False
has_previous = False
for e in coordinates:
if (e[4] == "entry"):
n_elements += 1
continue
if (e[4] == "right"):
has_next = True
continue
if (e[4] == "left"):
has_previous = True
continue
counter = 0
for element in coordinates:
xl = int(element[0])
yt = int(element[1])
xr = int(element[2])
yb = int(element[3])
if (xl % 2) == 1:
xl += 1
if (yt % 2) == 1:
yt += 1
if (xr % 2) == 1:
xr -= 1
if (yb % 2) == 1:
yb -= 1
if (element[4] == "left"):
entry_data["left"] = "boton" + str(n_page) + "x" + str(counter)
elif (element[4] == "right"):
entry_data["right"] = "boton" + \
str(n_page) + "x" + str(counter)
else:
entry_data["chapters"].append(
"boton" + str(n_page) + "x" + str(counter))
xml_file.write('\t\t\t\n')
counter += 1
xml_file.write("\n\n\n")
xml_file.close()
return entry_data
def create_dvd_menus(self, file_list, base_path):
self.file_list = file_list
self.refresh_static_data()
cv = devedeng.converter.converter.get_converter()
menu_folder = os.path.join(base_path, "menu")
try:
os.makedirs(menu_folder)
except:
pass
n_page = 0
self.pages = 1
processes = []
menu_entries = []
menu_converter = cv.get_menu_converter()
while n_page < self.pages:
self.sf = None
coordinates = self.paint_menu(True, False, False, n_page)
self.sf.write_to_png(os.path.join(
menu_folder, "menu_" + str(n_page) + "_bg.png"))
self.sf = None
self.paint_menu(False, False, False, n_page)
self.sf.write_to_png(os.path.join(
menu_folder, "menu_" + str(n_page) + "_unselected_bg.png"))
self.sf = None
self.paint_menu(False, True, False, n_page)
self.sf.write_to_png(os.path.join(
menu_folder, "menu_" + str(n_page) + "_selected_bg.png"))
self.sf = None
self.paint_menu(False, False, True, n_page)
self.sf.write_to_png(os.path.join(
menu_folder, "menu_" + str(n_page) + "_active_bg.png"))
entry_data = self.create_menu_stream(
menu_folder, n_page, coordinates)
converter = menu_converter()
final_path = converter.create_menu_mpeg(
n_page, self.background_music, self.sound_length, self.config.PAL, self.video_rate, self.audio_rate, menu_folder, self.audio_mp2)
entry_data["filename"] = final_path
menu_entries.append(entry_data)
# add this process without dependencies
processes.append(converter)
n_page += 1
return processes, menu_entries
def store_menu(self):
return self.serialize()
def restore_menu(self, data):
self.unserialize(data)