# -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 ########################################################################## # OpenLP - Open Source Lyrics Projection # # ---------------------------------------------------------------------- # # Copyright (c) 2008-2019 OpenLP Developers # # ---------------------------------------------------------------------- # # This program 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. # # # # This program 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 . # ########################################################################## """ The :mod:`lib` module contains most of the components and libraries that make OpenLP work. """ import logging from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common.i18n import translate from openlp.core.common.path import Path log = logging.getLogger(__name__ + '.__init__') class ServiceItemContext(object): """ The context in which a Service Item is being generated """ Preview = 0 Live = 1 Service = 2 class ImageSource(object): """ This enumeration class represents different image sources. An image sources states where an image is used. This enumeration class is need in the context of the :class:~openlp.core.lib.imagemanager`. ``ImagePlugin`` This states that an image is being used by the image plugin. ``Theme`` This says, that the image is used by a theme. ``CommandPlugins`` This states that an image is being used by a command plugin. """ ImagePlugin = 1 Theme = 2 CommandPlugins = 3 class MediaType(object): """ An enumeration class for types of media. """ Audio = 1 Video = 2 class ServiceItemAction(object): """ Provides an enumeration for the required action moving between service items by left/right arrow keys """ Previous = 1 PreviousLastSlide = 2 Next = 3 class ItemCapabilities(object): """ Provides an enumeration of a service item's capabilities ``CanPreview`` The capability to allow the ServiceManager to add to the preview tab when making the previous item live. ``CanEdit`` The capability to allow the ServiceManager to allow the item to be edited ``CanMaintain`` The capability to allow the ServiceManager to allow the item to be reordered. ``RequiresMedia`` Determines is the service_item needs a Media Player ``CanLoop`` The capability to allow the SlideController to allow the loop processing. ``CanAppend`` The capability to allow the ServiceManager to add leaves to the item ``NoLineBreaks`` The capability to remove lines breaks in the renderer ``OnLoadUpdate`` The capability to update MediaManager when a service Item is loaded. ``AddIfNewItem`` Not Used ``ProvidesOwnDisplay`` The capability to tell the SlideController the service Item has a different display. ``HasDetailedTitleDisplay`` Being Removed and decommissioned. ``HasVariableStartTime`` The capability to tell the ServiceManager that a change to start time is possible. ``CanSoftBreak`` The capability to tell the renderer that Soft Break is allowed ``CanWordSplit`` The capability to tell the renderer that it can split words is allowed ``HasBackgroundAudio`` That a audio file is present with the text. ``CanAutoStartForLive`` The capability to ignore the do not play if display blank flag. ``CanEditTitle`` The capability to edit the title of the item ``IsOptical`` Determines is the service_item is based on an optical device ``HasDisplayTitle`` The item contains 'displaytitle' on every frame which should be preferred over 'title' when displaying the item ``HasNotes`` The item contains 'notes' ``HasThumbnails`` The item has related thumbnails available ``HasMetaData`` The item has Meta Data about item """ CanPreview = 1 CanEdit = 2 CanMaintain = 3 RequiresMedia = 4 CanLoop = 5 CanAppend = 6 NoLineBreaks = 7 OnLoadUpdate = 8 AddIfNewItem = 9 ProvidesOwnDisplay = 10 # HasDetailedTitleDisplay = 11 HasVariableStartTime = 12 CanSoftBreak = 13 CanWordSplit = 14 HasBackgroundAudio = 15 CanAutoStartForLive = 16 CanEditTitle = 17 IsOptical = 18 HasDisplayTitle = 19 HasNotes = 20 HasThumbnails = 21 HasMetaData = 22 def get_text_file_string(text_file_path): """ Open a file and return its content as a string. If the supplied file path is not a file then the function returns False. If there is an error loading the file or the content can't be decoded then the function will return None. :param openlp.core.common.path.Path text_file_path: The path to the file. :return: The contents of the file, False if the file does not exist, or None if there is an Error reading or decoding the file. :rtype: str | False | None """ if not text_file_path.is_file(): return False content = None try: with text_file_path.open('r', encoding='utf-8') as file_handle: if file_handle.read(3) != '\xEF\xBB\xBF': # no BOM was found file_handle.seek(0) content = file_handle.read() except (OSError, UnicodeError): log.exception('Failed to open text file {text}'.format(text=text_file_path)) return content def str_to_bool(string_value): """ Convert a string version of a boolean into a real boolean. :param string_value: The string value to examine and convert to a boolean type. :return: The correct boolean value """ if isinstance(string_value, bool): return string_value return str(string_value).strip().lower() in ('true', 'yes', 'y') def build_icon(icon): """ Build a QIcon instance from an existing QIcon, a resource location, or a physical file location. If the icon is a QIcon instance, that icon is simply returned. If not, it builds a QIcon instance from the resource or file name. :param QtGui.QIcon | Path | QtGui.QIcon | str icon: The icon to build. This can be a QIcon, a resource string in the form ``:/resource/file.png``, or a file path location like ``Path(/path/to/file.png)``. However, the **recommended** way is to specify a resource string. :return: The build icon. :rtype: QtGui.QIcon """ if isinstance(icon, QtGui.QIcon): return icon pix_map = None button_icon = QtGui.QIcon() if isinstance(icon, str): pix_map = QtGui.QPixmap(icon) elif isinstance(icon, Path): pix_map = QtGui.QPixmap(str(icon)) elif isinstance(icon, QtGui.QImage): pix_map = QtGui.QPixmap.fromImage(icon) if pix_map: button_icon.addPixmap(pix_map, QtGui.QIcon.Normal, QtGui.QIcon.Off) return button_icon def image_to_byte(image, base_64=True): """ Resize an image to fit on the current screen for the web and returns it as a byte stream. :param image: The image to be converted. :param base_64: If True returns the image as Base64 bytes, otherwise the image is returned as a byte array. To preserve original intention, this defaults to True """ log.debug('image_to_byte - start') byte_array = QtCore.QByteArray() # use buffer to store pixmap into byteArray buffer = QtCore.QBuffer(byte_array) buffer.open(QtCore.QIODevice.WriteOnly) image.save(buffer, "PNG") log.debug('image_to_byte - end') if not base_64: return byte_array # convert to base64 encoding so does not get missed! return bytes(byte_array.toBase64()).decode('utf-8') def create_thumb(image_path, thumb_path, return_icon=True, size=None): """ Create a thumbnail from the given image path and depending on ``return_icon`` it returns an icon from this thumb. :param openlp.core.common.path.Path image_path: The image file to create the icon from. :param openlp.core.common.path.Path thumb_path: The filename to save the thumbnail to. :param return_icon: States if an icon should be build and returned from the thumb. Defaults to ``True``. :param size: Allows to state a own size (QtCore.QSize) to use. Defaults to ``None``, which means that a default height of 88 is used. :return: The final icon. """ reader = QtGui.QImageReader(str(image_path)) if size is None: # No size given; use default height of 88 if reader.size().isEmpty(): ratio = 1 else: ratio = reader.size().width() / reader.size().height() reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88)) elif size.isValid(): # Complete size given reader.setScaledSize(size) else: # Invalid size given if reader.size().isEmpty(): ratio = 1 else: ratio = reader.size().width() / reader.size().height() if size.width() >= 0: # Valid width; scale height reader.setScaledSize(QtCore.QSize(size.width(), int(size.width() / ratio))) elif size.height() >= 0: # Valid height; scale width reader.setScaledSize(QtCore.QSize(int(ratio * size.height()), size.height())) else: # Invalid; use default height of 88 reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88)) thumb = reader.read() thumb.save(str(thumb_path), thumb_path.suffix[1:].lower()) if not return_icon: return if thumb_path.exists(): return build_icon(thumb_path) # Fallback for files with animation support. return build_icon(image_path) def validate_thumb(file_path, thumb_path): """ Validates whether an file's thumb still exists and if is up to date. **Note**, you must **not** call this function, before checking the existence of the file. :param openlp.core.common.path.Path file_path: The path to the file. The file **must** exist! :param openlp.core.common.path.Path thumb_path: The path to the thumb. :return: Has the image changed since the thumb was created? :rtype: bool """ if not thumb_path.exists(): return False image_date = file_path.stat().st_mtime thumb_date = thumb_path.stat().st_mtime return image_date <= thumb_date def resize_image(image_path, width, height, background='#000000', ignore_aspect_ratio=False): """ Resize an image to fit on the current screen. DO NOT REMOVE THE DEFAULT BACKGROUND VALUE! :param image_path: The path to the image to resize. :param width: The new image width. :param height: The new image height. :param background: The background colour. Defaults to black. """ log.debug('resize_image - start') reader = QtGui.QImageReader(image_path) # The image's ratio. image_ratio = reader.size().width() / reader.size().height() resize_ratio = width / height # Figure out the size we want to resize the image to (keep aspect ratio). if image_ratio == resize_ratio or ignore_aspect_ratio: size = QtCore.QSize(width, height) elif image_ratio < resize_ratio: # Use the image's height as reference for the new size. size = QtCore.QSize(image_ratio * height, height) else: # Use the image's width as reference for the new size. size = QtCore.QSize(width, 1 / (image_ratio / width)) reader.setScaledSize(size) preview = reader.read() if image_ratio == resize_ratio: # We neither need to centre the image nor add "bars" to the image. return preview real_width = preview.width() real_height = preview.height() # and move it to the centre of the preview space new_image = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32_Premultiplied) painter = QtGui.QPainter(new_image) painter.fillRect(new_image.rect(), QtGui.QColor(background)) painter.drawImage((width - real_width) // 2, (height - real_height) // 2, preview) return new_image def check_item_selected(list_widget, message): """ Check if a list item is selected so an action may be performed on it :param list_widget: The list to check for selected items :param message: The message to give the user if no item is selected """ if not list_widget.selectedIndexes(): QtWidgets.QMessageBox.information(list_widget.parent(), translate('OpenLP.MediaManagerItem', 'No Items Selected'), message) return False return True def create_separated_list(string_list): """ Returns a string that represents a join of a list of strings with a localized separator. Localized separation will be done via the translate() function by the translators. :param string_list: List of unicode strings :return: Formatted string """ list_length = len(string_list) if list_length == 1: list_to_string = string_list[0] elif list_length == 2: list_to_string = translate('OpenLP.core.lib', '{one} and {two}').format(one=string_list[0], two=string_list[1]) elif list_length > 2: list_to_string = translate('OpenLP.core.lib', '{first} and {last}').format(first=', '.join(string_list[:-1]), last=string_list[-1]) else: list_to_string = '' return list_to_string