openlp/openlp/core/lib/__init__.py

479 lines
17 KiB
Python
Raw Normal View History

2010-09-10 19:47:33 +00:00
# -*- coding: utf-8 -*-
2012-12-27 16:27:59 +00:00
2019-04-13 13:00:22 +00:00
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
2022-02-01 10:10:57 +00:00
# Copyright (c) 2008-2022 OpenLP Developers #
2019-04-13 13:00:22 +00:00
# ---------------------------------------------------------------------- #
# 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 <https://www.gnu.org/licenses/>. #
##########################################################################
2010-09-10 19:47:33 +00:00
"""
The :mod:`lib` module contains most of the components and libraries that make
OpenLP work.
"""
import logging
import os
import base64
from enum import IntEnum
from pathlib import Path
2010-09-10 19:47:33 +00:00
from PyQt5 import QtCore, QtGui, QtWidgets
2015-11-07 00:49:40 +00:00
from openlp.core.common.i18n import UiStrings, translate
2013-10-13 20:36:42 +00:00
2014-04-12 20:19:22 +00:00
log = logging.getLogger(__name__ + '.__init__')
2010-09-10 19:47:33 +00:00
2013-03-23 07:07:06 +00:00
class DataType(IntEnum):
U8 = 1
U16 = 2
U32 = 4
class ServiceItemContext(object):
"""
The context in which a Service Item is being generated
"""
Preview = 0
Live = 1
Service = 2
2012-07-01 18:41:59 +00:00
class ImageSource(object):
"""
2013-03-07 12:30:24 +00:00
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`.
2012-07-01 19:41:12 +00:00
``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.
2012-07-01 18:41:59 +00:00
"""
ImagePlugin = 1
Theme = 2
CommandPlugins = 3
2012-07-01 18:41:59 +00:00
class MediaType(object):
"""
An enumeration class for types of media.
"""
Audio = 1
Video = 2
class ServiceItemAction(object):
"""
2013-03-07 12:30:24 +00:00
Provides an enumeration for the required action moving between service items by left/right arrow keys
"""
Previous = 1
PreviousLastSlide = 2
Next = 3
2012-04-22 19:50:18 +00:00
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
``CanStream``
The item requires to process a VLC Stream
``HasBackgroundVideo``
That a video file is present with the text
``HasBackgroundStream``
That a video stream is present with the text
``ProvidesOwnTheme``
The capability to tell the SlideController to force the use of the service item theme.
"""
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
2019-06-11 19:48:34 +00:00
CanStream = 23
HasBackgroundVideo = 24
HasBackgroundStream = 25
ProvidesOwnTheme = 26
def get_text_file_string(text_file_path):
2010-09-10 19:47:33 +00:00
"""
Open a file and return its content as a string. If the supplied file path is not a file then the function
2013-03-07 12:30:24 +00:00
returns False. If there is an error loading the file or the content can't be decoded then the function will return
None.
2010-09-10 19:47:33 +00:00
:param 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
2010-09-10 19:47:33 +00:00
"""
if not text_file_path.is_file():
2010-09-10 19:47:33 +00:00
return False
2013-03-27 09:25:39 +00:00
content = None
2010-09-10 19:47:33 +00:00
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))
2013-03-27 09:25:39 +00:00
return content
2010-09-10 19:47:33 +00:00
2012-04-22 19:50:18 +00:00
2013-03-07 12:30:24 +00:00
def str_to_bool(string_value):
2010-09-10 19:47:33 +00:00
"""
Convert a string version of a boolean into a real boolean.
2014-01-01 09:33:07 +00:00
:param string_value: The string value to examine and convert to a boolean type.
2015-09-08 19:13:59 +00:00
:return: The correct boolean value
2010-09-10 19:47:33 +00:00
"""
2013-03-07 12:30:24 +00:00
if isinstance(string_value, bool):
return string_value
2013-08-31 18:17:38 +00:00
return str(string_value).strip().lower() in ('true', 'yes', 'y')
2010-09-10 19:47:33 +00:00
2012-04-22 19:50:18 +00:00
2010-09-10 19:47:33 +00:00
def build_icon(icon):
"""
2013-03-07 12:30:24 +00:00
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.
2010-09-10 19:47:33 +00:00
: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.
2015-09-08 19:13:59 +00:00
:return: The build icon.
:rtype: QtGui.QIcon
2010-09-10 19:47:33 +00:00
"""
if isinstance(icon, QtGui.QIcon):
2016-10-27 17:45:50 +00:00
return icon
2016-10-30 08:29:22 +00:00
pix_map = None
2016-10-27 17:45:50 +00:00
button_icon = QtGui.QIcon()
if isinstance(icon, str):
2016-10-30 08:29:22 +00:00
pix_map = QtGui.QPixmap(icon)
elif isinstance(icon, Path):
pix_map = QtGui.QPixmap(str(icon))
elif isinstance(icon, QtGui.QImage): # pragma: no cover
2016-10-27 17:45:50 +00:00
pix_map = QtGui.QPixmap.fromImage(icon)
2016-10-30 08:29:22 +00:00
if pix_map:
button_icon.addPixmap(pix_map, QtGui.QIcon.Normal, QtGui.QIcon.Off)
2010-09-10 19:47:33 +00:00
return button_icon
2012-04-22 19:50:18 +00:00
def image_to_byte(image, base_64=True):
2010-09-10 19:47:33 +00:00
"""
2013-03-07 12:30:24 +00:00
Resize an image to fit on the current screen for the web and returns it as a byte stream.
2010-09-10 19:47:33 +00:00
:param image: The image to be converted.
2014-01-09 19:52:20 +00:00
: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
2010-09-10 19:47:33 +00:00
"""
2013-08-31 18:17:38 +00:00
log.debug('image_to_byte - start')
2010-09-10 19:47:33 +00:00
byte_array = QtCore.QByteArray()
# use buffer to store pixmap into byteArray
buffer = QtCore.QBuffer(byte_array)
buffer.open(QtCore.QIODevice.WriteOnly)
image.save(buffer, "JPEG")
2013-08-31 18:17:38 +00:00
log.debug('image_to_byte - end')
if not base_64:
return byte_array
2010-09-10 19:47:33 +00:00
# convert to base64 encoding so does not get missed!
return base64.b64encode(byte_array).decode('utf-8')
2010-09-10 19:47:33 +00:00
2012-04-22 19:50:18 +00:00
def image_to_data_uri(image_path):
"""
Converts a image into a base64 data uri
"""
extension = image_path.suffix.replace('.', '')
with open(image_path, 'rb') as image_file:
image_bytes = image_file.read()
image_base64 = base64.b64encode(image_bytes).decode('utf-8')
return 'data:image/{extension};base64,{data}'.format(extension=extension, data=image_base64)
2011-06-12 15:59:46 +00:00
def create_thumb(image_path, thumb_path, return_icon=True, size=None):
2011-06-12 15:17:01 +00:00
"""
2013-03-07 12:30:24 +00:00
Create a thumbnail from the given image path and depending on ``return_icon`` it returns an icon from this thumb.
2011-06-12 15:17:01 +00:00
:param Path image_path: The image file to create the icon from.
:param Path thumb_path: The filename to save the thumbnail to.
2014-01-01 09:33:07 +00:00
: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.
2015-09-08 19:13:59 +00:00
:return: The final icon.
2011-06-12 15:17:01 +00:00
"""
2017-11-18 11:23:15 +00:00
reader = QtGui.QImageReader(str(image_path))
2011-06-12 15:59:46 +00:00
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()
2011-06-12 15:59:46 +00:00
reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
elif size.isValid():
# Complete size given
2011-06-12 15:59:46 +00:00
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))
2011-06-12 15:17:01 +00:00
thumb = reader.read()
2017-11-18 11:23:15 +00:00
thumb.save(str(thumb_path), thumb_path.suffix[1:].lower())
2011-06-12 15:17:01 +00:00
if not return_icon:
return
2017-11-18 11:23:15 +00:00
if thumb_path.exists():
2013-10-28 21:23:17 +00:00
return build_icon(thumb_path)
2011-06-12 15:17:01 +00:00
# Fallback for files with animation support.
2013-10-28 21:23:17 +00:00
return build_icon(image_path)
2011-06-12 15:17:01 +00:00
2012-04-22 19:50:18 +00:00
2011-06-12 15:59:46 +00:00
def validate_thumb(file_path, thumb_path):
2011-06-12 15:17:01 +00:00
"""
2013-03-07 12:30:24 +00:00
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.
2011-06-12 15:17:01 +00:00
:param Path file_path: The path to the file. The file **must** exist!
:param Path thumb_path: The path to the thumb.
2017-09-17 19:43:15 +00:00
:return: Has the image changed since the thumb was created?
:rtype: bool
2011-06-12 15:17:01 +00:00
"""
if not thumb_path.exists():
2011-06-12 15:17:01 +00:00
return False
image_date = file_path.stat().st_mtime
thumb_date = thumb_path.stat().st_mtime
2011-06-12 15:17:01 +00:00
return image_date <= thumb_date
2012-04-22 19:50:18 +00:00
def resize_image(image_path, width, height, background='#000000', ignore_aspect_ratio=False):
2010-09-10 19:47:33 +00:00
"""
Resize an image to fit on the current screen.
DO NOT REMOVE THE DEFAULT BACKGROUND VALUE!
2014-01-01 09:33:07 +00:00
: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.
2010-09-10 19:47:33 +00:00
"""
2013-08-31 18:17:38 +00:00
log.debug('resize_image - start')
2011-06-09 10:41:02 +00:00
reader = QtGui.QImageReader(image_path)
2011-06-07 07:30:50 +00:00
# The image's ratio.
2013-04-24 19:05:34 +00:00
image_ratio = reader.size().width() / reader.size().height()
resize_ratio = width / height
2011-06-07 07:30:50 +00:00
# Figure out the size we want to resize the image to (keep aspect ratio).
if image_ratio == resize_ratio or ignore_aspect_ratio:
2011-06-07 07:30:50 +00:00
size = QtCore.QSize(width, height)
elif image_ratio < resize_ratio:
# Use the image's height as reference for the new size.
size = QtCore.QSize(int(image_ratio * height), height)
2010-10-15 15:33:06 +00:00
else:
2011-06-07 07:30:50 +00:00
# Use the image's width as reference for the new size.
size = QtCore.QSize(width, int(1 / (image_ratio / width)))
2011-06-07 07:30:50 +00:00
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
2012-07-01 18:45:14 +00:00
real_width = preview.width()
real_height = preview.height()
2010-09-10 19:47:33 +00:00
# and move it to the centre of the preview space
2012-12-28 22:06:43 +00:00
new_image = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32_Premultiplied)
2010-09-10 19:47:33 +00:00
painter = QtGui.QPainter(new_image)
painter.fillRect(new_image.rect(), QtGui.QColor(background))
2013-04-24 19:05:34 +00:00
painter.drawImage((width - real_width) // 2, (height - real_height) // 2, preview)
2010-09-10 19:47:33 +00:00
return new_image
2012-04-22 19:50:18 +00:00
2010-09-10 19:47:33 +00:00
def check_item_selected(list_widget, message):
"""
Check if a list item is selected so an action may be performed on it
2014-01-01 09:33:07 +00:00
:param list_widget: The list to check for selected items
:param message: The message to give the user if no item is selected
2010-09-10 19:47:33 +00:00
"""
if not list_widget.selectedIndexes():
2015-11-07 00:49:40 +00:00
QtWidgets.QMessageBox.information(list_widget.parent(),
translate('OpenLP.MediaManagerItem', 'No Items Selected'), message)
2010-09-10 19:47:33 +00:00
return False
return True
2012-04-22 19:50:18 +00:00
2013-10-13 17:23:52 +00:00
def create_separated_list(string_list):
2012-02-16 20:36:35 +00:00
"""
2016-10-04 00:03:15 +00:00
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.
2014-01-01 09:33:07 +00:00
:param string_list: List of unicode strings
:return: Formatted string
2012-02-16 20:36:35 +00:00
"""
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:
2016-11-21 20:56:48 +00:00
list_to_string = translate('OpenLP.core.lib', '{first} and {last}').format(first=', '.join(string_list[:-1]),
last=string_list[-1])
2012-02-16 20:36:35 +00:00
else:
list_to_string = ''
2018-09-01 00:27:53 +00:00
return list_to_string
def read_or_fail(file_object, length):
"""
Ensure that the data read is as the exact length requested. Otherwise raise an OSError.
:param io.IOBase file_object: The file-lke object ot read from.
:param int length: The length of the data to read.
:return: The data read.
"""
data = file_object.read(length)
if len(data) != length:
raise OSError(UiStrings().FileCorrupt)
return data
def read_int(file_object, data_type, endian='big'):
"""
Read the correct amount of data from a file-like object to decode it to the specified type.
:param io.IOBase file_object: The file-like object to read from.
:param DataType data_type: A member from the :enum:`DataType`
:param endian: The endianess of the data to be read
:return int: The decoded int
"""
data = read_or_fail(file_object, data_type)
return int.from_bytes(data, endian)
def seek_or_fail(file_object, offset, how=os.SEEK_SET):
"""
See to a set position and return an error if the cursor has not moved to that position.
:param io.IOBase file_object: The file-like object to attempt to seek.
:param int offset: The offset / position to seek by / to.
:param [os.SEEK_CUR | os.SEEK_SET how: Currently only supports os.SEEK_CUR (0) or os.SEEK_SET (1)
:return int: The new position in the file.
"""
if how not in (os.SEEK_CUR, os.SEEK_SET):
raise NotImplementedError
prev_pos = file_object.tell()
new_pos = file_object.seek(offset, how)
if how == os.SEEK_SET and new_pos != offset or how == os.SEEK_CUR and new_pos != prev_pos + offset:
raise OSError(UiStrings().FileCorrupt)
return new_pos