forked from openlp/openlp
Fix path of presentation thumbnail when loading from service files. Fixes bug 1463703.
Fix PDF reader using wrong maindisplay size. Fix log-traceback when trying to remove servicemanager files before eg. powerpoint has closed the files. Improve network exception handling. Fix timer that checks for presentation slide change, remove powerpoint event handler that did the same. Hide the main impress window when loading a presentation, but make it visible for a moment when starting presentation. Fixes bug 1420356. Improve the detection of powerpoints presentation window by only looking at the filename root. Fix traceback when going live with presentation while blanked to desktop. Set service_item.name to "images" for pdfs. bzr-revno: 2544 Fixes: https://launchpad.net/bugs/1463703, https://launchpad.net/bugs/1420356
This commit is contained in:
commit
f071284b0f
@ -33,7 +33,7 @@ import ntpath
|
|||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from openlp.core.common import RegistryProperties, Settings, translate, AppLocation
|
from openlp.core.common import RegistryProperties, Settings, translate, AppLocation, md5_hash
|
||||||
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, create_thumb
|
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, create_thumb
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -326,6 +326,12 @@ class ServiceItem(RegistryProperties):
|
|||||||
# If the item should have a display title but this frame doesn't have one, we make one up
|
# If the item should have a display title but this frame doesn't have one, we make one up
|
||||||
if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
|
if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
|
||||||
display_title = translate('OpenLP.ServiceItem', '[slide %d]') % (len(self._raw_frames) + 1)
|
display_title = translate('OpenLP.ServiceItem', '[slide %d]') % (len(self._raw_frames) + 1)
|
||||||
|
# Update image path to match servicemanager location if file was loaded from service
|
||||||
|
if image and not self.has_original_files and self.name == 'presentations':
|
||||||
|
file_location = os.path.join(path, file_name)
|
||||||
|
file_location_hash = md5_hash(file_location.encode('utf-8'))
|
||||||
|
image = os.path.join(AppLocation.get_section_data_path(self.name), 'thumbnails',
|
||||||
|
file_location_hash, ntpath.basename(image))
|
||||||
self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
|
self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
|
||||||
'display_title': display_title, 'notes': notes})
|
'display_title': display_title, 'notes': notes})
|
||||||
self._new_item()
|
self._new_item()
|
||||||
|
@ -1106,8 +1106,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
self.image_manager.stop_manager = True
|
self.image_manager.stop_manager = True
|
||||||
while self.image_manager.image_thread.isRunning():
|
while self.image_manager.image_thread.isRunning():
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
# Clean temporary files used by services
|
|
||||||
self.service_manager_contents.clean_up()
|
|
||||||
if save_settings:
|
if save_settings:
|
||||||
if Settings().value('advanced/save current plugin'):
|
if Settings().value('advanced/save current plugin'):
|
||||||
Settings().setValue('advanced/current media plugin', self.media_tool_box.currentIndex())
|
Settings().setValue('advanced/current media plugin', self.media_tool_box.currentIndex())
|
||||||
@ -1124,6 +1122,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
if self.live_controller.display:
|
if self.live_controller.display:
|
||||||
self.live_controller.display.close()
|
self.live_controller.display.close()
|
||||||
self.live_controller.display = None
|
self.live_controller.display = None
|
||||||
|
# Clean temporary files used by services
|
||||||
|
self.service_manager_contents.clean_up()
|
||||||
if is_win():
|
if is_win():
|
||||||
# Needed for Windows to stop crashes on exit
|
# Needed for Windows to stop crashes on exit
|
||||||
Registry().remove('application')
|
Registry().remove('application')
|
||||||
|
@ -24,6 +24,7 @@ The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP.
|
|||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
|
from http.client import HTTPException
|
||||||
import logging
|
import logging
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
@ -414,6 +415,11 @@ def get_web_page(url, header=None, update_openlp=False):
|
|||||||
page = None
|
page = None
|
||||||
if retries > CONNECTION_RETRIES:
|
if retries > CONNECTION_RETRIES:
|
||||||
raise
|
raise
|
||||||
|
except socket.gaierror:
|
||||||
|
log.exception('Socket gaierror: {}'.format(url))
|
||||||
|
page = None
|
||||||
|
if retries > CONNECTION_RETRIES:
|
||||||
|
raise
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
log.exception('ConnectionRefused: {}'.format(url))
|
log.exception('ConnectionRefused: {}'.format(url))
|
||||||
page = None
|
page = None
|
||||||
@ -425,6 +431,11 @@ def get_web_page(url, header=None, update_openlp=False):
|
|||||||
page = None
|
page = None
|
||||||
if retries > CONNECTION_RETRIES:
|
if retries > CONNECTION_RETRIES:
|
||||||
raise
|
raise
|
||||||
|
except HTTPException:
|
||||||
|
log.exception('HTTPException error: {}'.format(url))
|
||||||
|
page = None
|
||||||
|
if retries > CONNECTION_RETRIES:
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
# Don't know what's happening, so reraise the original
|
# Don't know what's happening, so reraise the original
|
||||||
raise
|
raise
|
||||||
|
@ -748,7 +748,10 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
|
|||||||
"""
|
"""
|
||||||
if not reference_url:
|
if not reference_url:
|
||||||
return None
|
return None
|
||||||
|
try:
|
||||||
page = get_web_page(reference_url, header, True)
|
page = get_web_page(reference_url, header, True)
|
||||||
|
except:
|
||||||
|
page = None
|
||||||
if not page:
|
if not page:
|
||||||
send_error_message('download')
|
send_error_message('download')
|
||||||
return None
|
return None
|
||||||
|
@ -35,7 +35,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from openlp.core.common import is_win
|
from openlp.core.common import is_win, Registry
|
||||||
|
|
||||||
if is_win():
|
if is_win():
|
||||||
from win32com.client import Dispatch
|
from win32com.client import Dispatch
|
||||||
@ -231,21 +231,13 @@ class ImpressDocument(PresentationDocument):
|
|||||||
return False
|
return False
|
||||||
self.desktop = desktop
|
self.desktop = desktop
|
||||||
properties = []
|
properties = []
|
||||||
if not is_win():
|
properties.append(self.create_property('Hidden', True))
|
||||||
# Recent versions of Impress on Windows won't start the presentation if it starts as minimized. It seems OK
|
|
||||||
# on Linux though.
|
|
||||||
properties.append(self.create_property('Minimized', True))
|
|
||||||
properties = tuple(properties)
|
properties = tuple(properties)
|
||||||
try:
|
try:
|
||||||
self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties)
|
self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties)
|
||||||
except:
|
except:
|
||||||
log.warning('Failed to load presentation %s' % url)
|
log.warning('Failed to load presentation %s' % url)
|
||||||
return False
|
return False
|
||||||
if is_win():
|
|
||||||
# As we can't start minimized the Impress window gets in the way.
|
|
||||||
# Either window.setPosSize(0, 0, 200, 400, 12) or .setVisible(False)
|
|
||||||
window = self.document.getCurrentController().getFrame().getContainerWindow()
|
|
||||||
window.setVisible(False)
|
|
||||||
self.presentation = self.document.getPresentation()
|
self.presentation = self.document.getPresentation()
|
||||||
self.presentation.Display = ScreenList().current['number'] + 1
|
self.presentation.Display = ScreenList().current['number'] + 1
|
||||||
self.control = None
|
self.control = None
|
||||||
@ -382,6 +374,8 @@ class ImpressDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
log.debug('start presentation OpenOffice')
|
log.debug('start presentation OpenOffice')
|
||||||
if self.control is None or not self.control.isRunning():
|
if self.control is None or not self.control.isRunning():
|
||||||
|
window = self.document.getCurrentController().getFrame().getContainerWindow()
|
||||||
|
window.setVisible(True)
|
||||||
self.presentation.start()
|
self.presentation.start()
|
||||||
self.control = self.presentation.getController()
|
self.control = self.presentation.getController()
|
||||||
# start() returns before the Component is ready. Try for 15 seconds.
|
# start() returns before the Component is ready. Try for 15 seconds.
|
||||||
@ -390,9 +384,13 @@ class ImpressDocument(PresentationDocument):
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
sleep_count += 1
|
sleep_count += 1
|
||||||
self.control = self.presentation.getController()
|
self.control = self.presentation.getController()
|
||||||
|
window.setVisible(False)
|
||||||
else:
|
else:
|
||||||
self.control.activate()
|
self.control.activate()
|
||||||
self.goto_slide(1)
|
self.goto_slide(1)
|
||||||
|
# Make sure impress doesn't steal focus, unless we're on a single screen setup
|
||||||
|
if len(ScreenList().screen_list) > 1:
|
||||||
|
Registry().get('main_window').activateWindow()
|
||||||
|
|
||||||
def get_slide_number(self):
|
def get_slide_number(self):
|
||||||
"""
|
"""
|
||||||
|
@ -281,6 +281,7 @@ class PresentationMediaItem(MediaManagerItem):
|
|||||||
service_item.add_capability(ItemCapabilities.CanPreview)
|
service_item.add_capability(ItemCapabilities.CanPreview)
|
||||||
service_item.add_capability(ItemCapabilities.CanLoop)
|
service_item.add_capability(ItemCapabilities.CanLoop)
|
||||||
service_item.add_capability(ItemCapabilities.CanAppend)
|
service_item.add_capability(ItemCapabilities.CanAppend)
|
||||||
|
service_item.name = 'images'
|
||||||
# force a nonexistent theme
|
# force a nonexistent theme
|
||||||
service_item.theme = -1
|
service_item.theme = -1
|
||||||
for bitem in items:
|
for bitem in items:
|
||||||
|
@ -243,6 +243,10 @@ class Controller(object):
|
|||||||
Instruct the controller to stop and hide the presentation.
|
Instruct the controller to stop and hide the presentation.
|
||||||
"""
|
"""
|
||||||
log.debug('Live = %s, stop' % self.is_live)
|
log.debug('Live = %s, stop' % self.is_live)
|
||||||
|
# The document has not been loaded yet, so don't do anything. This can happen when going live with a
|
||||||
|
# presentation while blanked to desktop.
|
||||||
|
if not self.doc:
|
||||||
|
return
|
||||||
# Save the current slide number to be able to return to this slide if the presentation is activated again.
|
# Save the current slide number to be able to return to this slide if the presentation is activated again.
|
||||||
if self.doc.is_active():
|
if self.doc.is_active():
|
||||||
self.doc.slidenumber = self.doc.get_slide_number()
|
self.doc.slidenumber = self.doc.get_slide_number()
|
||||||
@ -352,6 +356,7 @@ class MessageListener(object):
|
|||||||
self.controller = controller
|
self.controller = controller
|
||||||
else:
|
else:
|
||||||
controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
|
controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
|
||||||
|
self.timer.start()
|
||||||
|
|
||||||
def slide(self, message):
|
def slide(self, message):
|
||||||
"""
|
"""
|
||||||
@ -423,6 +428,7 @@ class MessageListener(object):
|
|||||||
is_live = message[1]
|
is_live = message[1]
|
||||||
if is_live:
|
if is_live:
|
||||||
self.live_handler.shutdown()
|
self.live_handler.shutdown()
|
||||||
|
self.timer.stop()
|
||||||
else:
|
else:
|
||||||
self.preview_handler.shutdown()
|
self.preview_handler.shutdown()
|
||||||
|
|
||||||
|
@ -89,11 +89,11 @@ class PdfController(PresentationController):
|
|||||||
# Analyse the output to see it the program is mudraw, ghostscript or neither
|
# Analyse the output to see it the program is mudraw, ghostscript or neither
|
||||||
for line in runlog.splitlines():
|
for line in runlog.splitlines():
|
||||||
decoded_line = line.decode()
|
decoded_line = line.decode()
|
||||||
found_mudraw = re.search('usage: mudraw.*', decoded_line)
|
found_mudraw = re.search('usage: mudraw.*', decoded_line, re.IGNORECASE)
|
||||||
if found_mudraw:
|
if found_mudraw:
|
||||||
program_type = 'mudraw'
|
program_type = 'mudraw'
|
||||||
break
|
break
|
||||||
found_gs = re.search('GPL Ghostscript.*', decoded_line)
|
found_gs = re.search('GPL Ghostscript.*', decoded_line, re.IGNORECASE)
|
||||||
if found_gs:
|
if found_gs:
|
||||||
program_type = 'gs'
|
program_type = 'gs'
|
||||||
break
|
break
|
||||||
@ -222,8 +222,8 @@ class PdfDocument(PresentationDocument):
|
|||||||
continue
|
continue
|
||||||
# Calculate the ratio from pdf to screen
|
# Calculate the ratio from pdf to screen
|
||||||
if width > 0 and height > 0:
|
if width > 0 and height > 0:
|
||||||
width_ratio = size.right() / width
|
width_ratio = size.width() / width
|
||||||
height_ratio = size.bottom() / height
|
height_ratio = size.height() / height
|
||||||
# return the resolution that should be used. 72 is default.
|
# return the resolution that should be used. 72 is default.
|
||||||
if width_ratio > height_ratio:
|
if width_ratio > height_ratio:
|
||||||
return int(height_ratio * 72)
|
return int(height_ratio * 72)
|
||||||
@ -254,7 +254,7 @@ class PdfDocument(PresentationDocument):
|
|||||||
if not os.path.isdir(self.get_temp_folder()):
|
if not os.path.isdir(self.get_temp_folder()):
|
||||||
os.makedirs(self.get_temp_folder())
|
os.makedirs(self.get_temp_folder())
|
||||||
if self.controller.mudrawbin:
|
if self.controller.mudrawbin:
|
||||||
runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()),
|
runlog = check_output([self.controller.mudrawbin, '-w', str(size.width()), '-h', str(size.height()),
|
||||||
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
|
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
|
||||||
startupinfo=self.startupinfo)
|
startupinfo=self.startupinfo)
|
||||||
elif self.controller.gsbin:
|
elif self.controller.gsbin:
|
||||||
|
@ -32,7 +32,7 @@ import time
|
|||||||
from openlp.core.common import is_win, Settings
|
from openlp.core.common import is_win, Settings
|
||||||
|
|
||||||
if is_win():
|
if is_win():
|
||||||
from win32com.client import DispatchWithEvents
|
from win32com.client import Dispatch
|
||||||
import win32com
|
import win32com
|
||||||
import win32con
|
import win32con
|
||||||
import winreg
|
import winreg
|
||||||
@ -93,22 +93,9 @@ class PowerpointController(PresentationController):
|
|||||||
"""
|
"""
|
||||||
Loads PowerPoint process.
|
Loads PowerPoint process.
|
||||||
"""
|
"""
|
||||||
class PowerPointEvents:
|
|
||||||
"""
|
|
||||||
Class to catch events from PowerPoint.
|
|
||||||
"""
|
|
||||||
def OnSlideShowNextClick(self, slideshow_window, effect):
|
|
||||||
"""
|
|
||||||
Occurs on the next click of the slide.
|
|
||||||
If the main OpenLP window is not in focus force update of the slidecontroller.
|
|
||||||
"""
|
|
||||||
if not Registry().get('main_window').isActiveWindow():
|
|
||||||
log.debug('main window is not in focus - should update slidecontroller')
|
|
||||||
Registry().execute('slidecontroller_live_change', slideshow_window.View.CurrentShowPosition)
|
|
||||||
|
|
||||||
log.debug('start_process')
|
log.debug('start_process')
|
||||||
if not self.process:
|
if not self.process:
|
||||||
self.process = DispatchWithEvents('PowerPoint.Application', PowerPointEvents)
|
self.process = Dispatch('PowerPoint.Application')
|
||||||
|
|
||||||
def kill(self):
|
def kill(self):
|
||||||
"""
|
"""
|
||||||
@ -330,6 +317,7 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
log.debug('start_presentation')
|
log.debug('start_presentation')
|
||||||
# SlideShowWindow measures its size/position by points, not pixels
|
# SlideShowWindow measures its size/position by points, not pixels
|
||||||
|
# https://technet.microsoft.com/en-us/library/dn528846.aspx
|
||||||
try:
|
try:
|
||||||
dpi = win32ui.GetActiveWindow().GetDC().GetDeviceCaps(88)
|
dpi = win32ui.GetActiveWindow().GetDC().GetDeviceCaps(88)
|
||||||
except win32ui.error:
|
except win32ui.error:
|
||||||
@ -378,8 +366,9 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.debug('compare size: %d and %d, %d and %d, %d and %d, %d and %d'
|
log.debug('compare size: %d and %d, %d and %d, %d and %d, %d and %d'
|
||||||
% (size.y(), top, size.height(), (bottom - top), size.x(), left, size.width(), (right - left)))
|
% (size.y(), top, size.height(), (bottom - top), size.x(), left, size.width(), (right - left)))
|
||||||
log.debug('window title: %s' % window_title)
|
log.debug('window title: %s' % window_title)
|
||||||
|
filename_root, filename_ext = os.path.splitext(os.path.basename(self.file_path))
|
||||||
if size.y() == top and size.height() == (bottom - top) and size.x() == left and \
|
if size.y() == top and size.height() == (bottom - top) and size.x() == left and \
|
||||||
size.width() == (right - left) and os.path.basename(self.file_path) in window_title:
|
size.width() == (right - left) and filename_root in window_title:
|
||||||
log.debug('Found a match and will save the handle')
|
log.debug('Found a match and will save the handle')
|
||||||
self.presentation_hwnd = hwnd
|
self.presentation_hwnd = hwnd
|
||||||
# Stop powerpoint from flashing in the taskbar
|
# Stop powerpoint from flashing in the taskbar
|
||||||
|
@ -128,7 +128,8 @@ class PresentationTab(SettingsTab):
|
|||||||
'Clicking on a selected slide in the slidecontroller advances to next effect.'))
|
'Clicking on a selected slide in the slidecontroller advances to next effect.'))
|
||||||
self.ppt_window_check_box.setText(
|
self.ppt_window_check_box.setText(
|
||||||
translate('PresentationPlugin.PresentationTab',
|
translate('PresentationPlugin.PresentationTab',
|
||||||
'Let PowerPoint control the size and position of the presentation window.'))
|
'Let PowerPoint control the size and position of the presentation window '
|
||||||
|
'(workaround for Windows 8 scaling issue).'))
|
||||||
self.pdf_program_check_box.setText(
|
self.pdf_program_check_box.setText(
|
||||||
translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
|
translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ from unittest import TestCase
|
|||||||
from tests.functional import MagicMock, patch
|
from tests.functional import MagicMock, patch
|
||||||
from tests.utils import assert_length, convert_file_service_item
|
from tests.utils import assert_length, convert_file_service_item
|
||||||
|
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry, md5_hash
|
||||||
from openlp.core.lib import ItemCapabilities, ServiceItem, ServiceItemType
|
from openlp.core.lib import ItemCapabilities, ServiceItem, ServiceItemType
|
||||||
|
|
||||||
VERSE = 'The Lord said to {r}Noah{/r}: \n'\
|
VERSE = 'The Lord said to {r}Noah{/r}: \n'\
|
||||||
@ -244,6 +244,33 @@ class TestServiceItem(TestCase):
|
|||||||
self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
|
self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
|
||||||
self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
|
self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
|
||||||
|
|
||||||
|
@patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path')
|
||||||
|
def add_from_command_for_a_presentation_thumb_test(self, mocked_get_section_data_path):
|
||||||
|
"""
|
||||||
|
Test the Service Item - adding a presentation, and updating the thumb path
|
||||||
|
"""
|
||||||
|
# GIVEN: A service item, a mocked AppLocation and presentation data
|
||||||
|
mocked_get_section_data_path.return_value = os.path.join('mocked', 'section', 'path')
|
||||||
|
service_item = ServiceItem(None)
|
||||||
|
service_item.has_original_files = False
|
||||||
|
service_item.name = 'presentations'
|
||||||
|
presentation_name = 'test.pptx'
|
||||||
|
thumb = os.path.join('tmp', 'test', 'thumb.png')
|
||||||
|
display_title = 'DisplayTitle'
|
||||||
|
notes = 'Note1\nNote2\n'
|
||||||
|
expected_thumb_path = os.path.join('mocked', 'section', 'path', 'thumbnails',
|
||||||
|
md5_hash(os.path.join(TEST_PATH, presentation_name).encode('utf-8')),
|
||||||
|
'thumb.png')
|
||||||
|
frame = {'title': presentation_name, 'image': expected_thumb_path, 'path': TEST_PATH,
|
||||||
|
'display_title': display_title, 'notes': notes}
|
||||||
|
|
||||||
|
# WHEN: adding presentation to service_item
|
||||||
|
service_item.add_from_command(TEST_PATH, presentation_name, thumb, display_title, notes)
|
||||||
|
|
||||||
|
# THEN: verify that it is setup as a Command and that the frame data matches
|
||||||
|
self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
|
||||||
|
self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
|
||||||
|
|
||||||
def service_item_load_optical_media_from_service_test(self):
|
def service_item_load_optical_media_from_service_test(self):
|
||||||
"""
|
"""
|
||||||
Test the Service Item - load an optical media item
|
Test the Service Item - load an optical media item
|
||||||
|
@ -135,5 +135,5 @@ class TestPdfController(TestCase, TestMixin):
|
|||||||
self.assertEqual(760, image.height(), 'The height should be 760')
|
self.assertEqual(760, image.height(), 'The height should be 760')
|
||||||
self.assertEqual(537, image.width(), 'The width should be 537')
|
self.assertEqual(537, image.width(), 'The width should be 537')
|
||||||
else:
|
else:
|
||||||
self.assertEqual(767, image.height(), 'The height should be 767')
|
self.assertEqual(768, image.height(), 'The height should be 768')
|
||||||
self.assertEqual(543, image.width(), 'The width should be 543')
|
self.assertEqual(543, image.width(), 'The width should be 543')
|
||||||
|
Loading…
Reference in New Issue
Block a user