# 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 version 3 # as published by the Free Software Foundation. # # 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_colorbutton("separator_color", (0, 0, 0, 1), self.update_preview) self.add_colorbutton("separator_shadow", (0, 0, 0, 0), self.update_preview) self.add_colorbutton("separator_background", (1, 1, 1, 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_group("menu_aspect_ratio", ["menu_aspect_4_3", "menu_aspect_16_9"], "menu_aspect_4_3", self.on_menu_aspect_changed) 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_separator": fgcolor = self.separator_color bgcolor = self.separator_shadow hard_borders = False font = self.entry_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.element_type == "file_movie": if element.show_in_menu: self.title_list.append((element, counter)) counter += 1 else: self.title_list.append((element, None)) 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: if self.to_png: self.sf = cairo.ImageSurface(cairo.FORMAT_ARGB32, 720, int(self.y)) else: self.sf = cairo.ImageSurface(cairo.FORMAT_ARGB32, 720, 540) self.cr = cairo.Context(self.sf) if not self.to_png: if self.menu_aspect_ratio == "menu_aspect_16_9": self.cr.scale(1.0,405.0 / self.y) else: self.cr.scale(1.0,540.0 / self.y) 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") 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) paint_arrows = False if n_entries > entries_per_page: paint_arrows = True else: for element in self.file_list: if ((element.element_type == "separator") and element.page_jump): paint_arrows = True break if paint_arrows: entries_per_page -= 1 self.pages = 1 n_entries = 1 self.page_list = [0] entry = 0 for element in self.file_list: if ((element.element_type == "separator") and element.page_jump) or (n_entries > entries_per_page): self.pages += 1 n_entries = 1 self.page_list.append(entry) entry += 1 n_entries += 1 if (page_number > self.pages) and (page_number > 0): page_number = self.pages - 1 if self.wcurrent_page is not None: self.wcurrent_page.set_text(_("Page {0} of {1}").format(page_number + 1, 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.background_color) 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_position in range(entries_per_page): entry_number = self.page_list[page_number] + entry_position if entry_number >= len(self.title_list): break entry = self.title_list[entry_number] if (entry_position != 0) and (entry[0].element_type == "separator") and (entry[0].page_jump): break if entry[0].element_type == "file_movie": text = entry[0].title_name is_movie = True coordinates.append([xl, y - height, xr, y + height, "entry"]) else: text = entry[0].separator_name is_movie = False if paint_background: self.paint_base(xl, xr, y, 0, self.background_color if is_movie else self.separator_background) self.write_text(text, "menu_entry" if is_movie else "menu_separator", xl, xr, y, self.position_horizontal) if paint_selected and is_movie: self.write_text(text, "menu_entry_selected", xl, xr, y, self.position_horizontal) if paint_activated and is_movie: 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.background_color) 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.background_color) 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.background_color) self.paint_base(xl, xr, y, 2, self.background_color) 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, color): 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(color[0], color[1], color[2], 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_menu_aspect_changed(self, b=None): 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.to_png = False 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 self.to_png = True 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, self.menu_aspect_ratio == "menu_aspect_16_9") 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): # check that the menu's background picture and music are there if ('background_picture' in data) and (not os.path.exists(data['background_picture'])): data['background_picture'] = self.default_background if ('background_music' in data) and (not os.path.exists(data['background_music'])): data['background_music'] = self.default_sound self.unserialize(data)