← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~trb143/openlp/media into lp:openlp

 

Tim Bentley has proposed merging lp:~trb143/openlp/media into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)
Related bugs:
  Bug #885150 in OpenLP: "Need non self contained service files"
  https://bugs.launchpad.net/openlp/+bug/885150
  Bug #899714 in OpenLP: "Play/Pause button should be merged"
  https://bugs.launchpad.net/openlp/+bug/899714
  Bug #927829 in OpenLP: "media backends should provide some information about themselves in the settings"
  https://bugs.launchpad.net/openlp/+bug/927829
  Bug #952821 in OpenLP: "Unable to play videos from web"
  https://bugs.launchpad.net/openlp/+bug/952821
  Bug #958198 in OpenLP: "Replacing live background with a video shows theme behind"
  https://bugs.launchpad.net/openlp/+bug/958198
  Bug #999618 in OpenLP: "Video position slider jumps to part way through video"
  https://bugs.launchpad.net/openlp/+bug/999618
  Bug #1022053 in OpenLP: "Previewing media item interferes with live media item"
  https://bugs.launchpad.net/openlp/+bug/1022053
  Bug #1063211 in OpenLP: "Media and Presentation Plugins do not update the service suffix lists if players are added or removed without a restart"
  https://bugs.launchpad.net/openlp/+bug/1063211

For more details, see:
https://code.launchpad.net/~trb143/openlp/media/+merge/144574

One bug fix in webvideo

Some new tests
Some code cleanups 
-- 
https://code.launchpad.net/~trb143/openlp/media/+merge/144574
Your team OpenLP Core is requested to review the proposed merge of lp:~trb143/openlp/media into lp:openlp.
=== modified file 'openlp/core/lib/serviceitem.py'
--- openlp/core/lib/serviceitem.py	2013-01-21 19:47:53 +0000
+++ openlp/core/lib/serviceitem.py	2013-01-23 19:58:22 +0000
@@ -161,7 +161,7 @@
         self.service_item_type = None
         self._raw_frames = []
         self._display_frames = []
-        self._uuid = 0
+        self.unique_identifier = 0
         self.notes = u''
         self.from_plugin = False
         self.capabilities = []
@@ -195,7 +195,7 @@
         Method to set the internal id of the item. This is used to compare
         service items to see if they are the same.
         """
-        self._uuid = unicode(uuid.uuid1())
+        self.unique_identifier = unicode(uuid.uuid1())
         self.validate_item()
 
     def add_capability(self, capability):
@@ -454,14 +454,14 @@
 
     def merge(self, other):
         """
-        Updates the _uuid with the value from the original one
-        The _uuid is unique for a given service item but this allows one to
+        Updates the unique_identifier with the value from the original one
+        The unique_identifier is unique for a given service item but this allows one to
         replace an original version.
 
         ``other``
             The service item to be merged with
         """
-        self._uuid = other._uuid
+        self.unique_identifier = other.unique_identifier
         self.notes = other.notes
         self.temporary_edit = other.temporary_edit
         # Copy theme over if present.
@@ -478,13 +478,13 @@
         """
         if not other:
             return False
-        return self._uuid == other._uuid
+        return self.unique_identifier == other.unique_identifier
 
     def __ne__(self, other):
         """
         Confirms the service items are not for the same instance
         """
-        return self._uuid != other._uuid
+        return self.unique_identifier != other.unique_identifier
 
     def is_media(self):
         """
@@ -637,10 +637,10 @@
             if self.is_image() and not os.path.exists((frame[u'path'])):
                 self.is_valid = False
             elif self.is_command():
-                file = os.path.join(frame[u'path'],frame[u'title'])
-                if not os.path.exists(file):
+                file_name = os.path.join(frame[u'path'], frame[u'title'])
+                if not os.path.exists(file_name):
                     self.is_valid = False
                 if suffix_list and not self.is_text():
-                    type = frame[u'title'].split(u'.')[-1]
-                    if type.lower() not in suffix_list:
+                    file_suffix = frame[u'title'].split(u'.')[-1]
+                    if file_suffix.lower() not in suffix_list:
                         self.is_valid = False

=== modified file 'openlp/core/ui/media/webkitplayer.py'
--- openlp/core/ui/media/webkitplayer.py	2013-01-20 12:23:22 +0000
+++ openlp/core/ui/media/webkitplayer.py	2013-01-23 19:58:22 +0000
@@ -411,13 +411,13 @@
         else:
             if display.frame.evaluateJavaScript(u'show_video("isEnded");') == 'true':
                 self.stop(display)
-            (currentTime, ok) = display.frame.evaluateJavaScript(u'show_video("currentTime");')
+            currentTime = display.frame.evaluateJavaScript(u'show_video("currentTime");')
             # check if conversion was ok and value is not 'NaN'
-            if ok and currentTime != float('inf'):
+            if currentTime and currentTime != float('inf'):
                 currentTime = int(currentTime * 1000)
-            (length, ok) = display.frame.evaluateJavaScript(u'show_video("length");')
+            length = display.frame.evaluateJavaScript(u'show_video("length");')
             # check if conversion was ok and value is not 'NaN'
-            if ok and length != float('inf'):
+            if length and length != float('inf'):
                 length = int(length * 1000)
         if currentTime > 0:
             controller.media_info.length = length

=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py	2013-01-21 19:47:53 +0000
+++ openlp/core/ui/servicemanager.py	2013-01-23 19:58:22 +0000
@@ -110,7 +110,7 @@
         self.suffixes = []
         self.dropPosition = 0
         self.expandTabs = False
-        self.serviceId = 0
+        self.service_id = 0
         # is a new service and has not been saved
         self._modified = False
         self._fileName = u''
@@ -165,8 +165,7 @@
         # Add the bottom toolbar
         self.orderToolbar = OpenLPToolbar(self)
         action_list = ActionList.get_instance()
-        action_list.add_category(
-            UiStrings().Service, CategoryOrder.standardToolbar)
+        action_list.add_category(UiStrings().Service, CategoryOrder.standardToolbar)
         self.serviceManagerList.moveTop = self.orderToolbar.addToolbarAction(u'moveTop',
             text=translate('OpenLP.ServiceManager', 'Move to &top'), icon=u':/services/service_top.png',
             tooltip=translate('OpenLP.ServiceManager', 'Move item to the top of the service.'),
@@ -297,7 +296,7 @@
         has been modified.
         """
         if modified:
-            self.serviceId += 1
+            self.service_id += 1
         self._modified = modified
         serviceFile = self.shortFileName() or translate('OpenLP.ServiceManager', 'Untitled Service')
         self.mainwindow.setServiceModified(modified, serviceFile)
@@ -394,6 +393,9 @@
         self.loadFile(fileName)
 
     def saveModifiedService(self):
+        """
+        Check to see if a service needs to be saved.
+        """
         return QtGui.QMessageBox.question(self.mainwindow,
             translate('OpenLP.ServiceManager', 'Modified Service'),
             translate('OpenLP.ServiceManager',
@@ -401,6 +403,9 @@
             QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Save)
 
     def onRecentServiceClicked(self):
+        """
+        Load a recent file as the service triggered by mainwindow recent service list.
+        """
         sender = self.sender()
         self.loadFile(sender.data())
 
@@ -411,7 +416,7 @@
         self.serviceManagerList.clear()
         self.serviceItems = []
         self.setFileName(u'')
-        self.serviceId += 1
+        self.service_id += 1
         self.setModified(False)
         Settings().setValue(u'servicemanager/last file', u'')
         Receiver.send_message(u'servicemanager_new_service')
@@ -478,38 +483,36 @@
             else:
                 service_item = item[u'service_item'].get_service_repr(self._saveLite)
                 if service_item[u'header'][u'background_audio']:
-                    for i, filename in enumerate(
-                        service_item[u'header'][u'background_audio']):
-                        new_file = os.path.join(u'audio',
-                            item[u'service_item']._uuid, filename)
+                    for i, filename in enumerate(service_item[u'header'][u'background_audio']):
+                        new_file = os.path.join(u'audio', item[u'service_item'].unique_identifier, filename)
                         audio_files.append((filename, new_file))
                         service_item[u'header'][u'background_audio'][i] = new_file
                 # Add the service item to the service.
                 service.append({u'serviceitem': service_item})
         self.repaintServiceList(-1, -1)
-        for file in write_list:
-            file_size = os.path.getsize(file)
+        for file_item in write_list:
+            file_size = os.path.getsize(file_item)
             total_size += file_size
-        log.debug(u'ServiceManager.saveFile - ZIP contents size is %i bytes' % total_size)
+        log.debug(u'ServiceManager.savefile - ZIP contents size is %i bytes' % total_size)
         service_content = cPickle.dumps(service)
         # Usual Zip file cannot exceed 2GiB, file with Zip64 cannot be
         # extracted using unzip in UNIX.
         allow_zip_64 = (total_size > 2147483648 + len(service_content))
         log.debug(u'ServiceManager.saveFile - allowZip64 is %s' % allow_zip_64)
-        zip = None
+        zip_file = None
         success = True
         self.mainwindow.incrementProgressBar()
         try:
-            zip = zipfile.ZipFile(temp_file_name, 'w', zipfile.ZIP_STORED, allow_zip_64)
+            zip_file = zipfile.ZipFile(temp_file_name, 'w', zipfile.ZIP_STORED, allow_zip_64)
             # First we add service contents.
             # We save ALL filenames into ZIP using UTF-8.
-            zip.writestr(service_file_name.encode(u'utf-8'), service_content)
+            zip_file.writestr(service_file_name.encode(u'utf-8'), service_content)
             # Finally add all the listed media files.
             for write_from in write_list:
-                zip.write(write_from, write_from.encode(u'utf-8'))
+                zip_file.write(write_from, write_from.encode(u'utf-8'))
             for audio_from, audio_to in audio_files:
                 if audio_from.startswith(u'audio'):
-                    # When items are saved, they get new UUID's. Let's copy the
+                    # When items are saved, they get new unique_identifier. Let's copy the
                     # file to the new location. Unused files can be ignored,
                     # OpenLP automatically cleans up the service manager dir on
                     # exit.
@@ -519,7 +522,7 @@
                 check_directory_exists(save_path)
                 if not os.path.exists(save_file):
                     shutil.copy(audio_from, save_file)
-                zip.write(audio_from, audio_to.encode(u'utf-8'))
+                zip_file.write(audio_from, audio_to.encode(u'utf-8'))
         except IOError:
             log.exception(u'Failed to save service to disk: %s', temp_file_name)
             Receiver.send_message(u'openlp_error_message', {
@@ -528,8 +531,8 @@
             })
             success = False
         finally:
-            if zip:
-                zip.close()
+            if zip_file:
+                zip_file.close()
         self.mainwindow.finishedProgressBar()
         Receiver.send_message(u'cursor_normal')
         if success:
@@ -544,12 +547,8 @@
 
     def saveLocalFile(self):
         """
-        Save the current service file.
-
-        A temporary file is created so that we don't overwrite the existing one
-        and leave a mangled service file should there be an error when saving.
-        No files are added to this version of the service as it is deisgned
-        to only work on the machine it was save on if there are files.
+        Save the current service file but leave all the file references alone to point to the current machine.
+        This format is not transportable as it will not contain any files.
         """
         if not self.fileName():
             return self.saveFileAs()
@@ -559,8 +558,8 @@
         log.debug(temp_file_name)
         path_file_name = unicode(self.fileName())
         path, file_name = os.path.split(path_file_name)
-        basename = os.path.splitext(file_name)[0]
-        service_file_name = '%s.osd' % basename
+        base_name = os.path.splitext(file_name)[0]
+        service_file_name = '%s.osd' % base_name
         log.debug(u'ServiceManager.saveFile - %s', path_file_name)
         Settings().setValue(self.mainwindow.serviceManagerSettingsSection + u'/last directory', path)
         service = []
@@ -574,14 +573,14 @@
             service.append({u'serviceitem': service_item})
             self.mainwindow.incrementProgressBar()
         service_content = cPickle.dumps(service)
-        zip = None
+        zip_file = None
         success = True
         self.mainwindow.incrementProgressBar()
         try:
-            zip = zipfile.ZipFile(temp_file_name, 'w', zipfile.ZIP_STORED,
+            zip_file = zipfile.ZipFile(temp_file_name, 'w', zipfile.ZIP_STORED,
                 True)
             # First we add service contents.
-            zip.writestr(service_file_name.encode(u'utf-8'), service_content)
+            zip_file.writestr(service_file_name.encode(u'utf-8'), service_content)
         except IOError:
             log.exception(u'Failed to save service to disk: %s', temp_file_name)
             Receiver.send_message(u'openlp_error_message', {
@@ -590,8 +589,8 @@
             })
             success = False
         finally:
-            if zip:
-                zip.close()
+            if zip_file:
+                zip_file.close()
         self.mainwindow.finishedProgressBar()
         Receiver.send_message(u'cursor_normal')
         if success:
@@ -705,11 +704,11 @@
                     else:
                         serviceItem.set_from_service(item, self.servicePath)
                     serviceItem.validate_item(self.suffixes)
-                    self.load_item_uuid = 0
+                    self.load_item_unique_identifier = 0
                     if serviceItem.is_capable(ItemCapabilities.OnLoadUpdate):
                         Receiver.send_message(u'%s_service_load' % serviceItem.name.lower(), serviceItem)
                     # if the item has been processed
-                    if serviceItem._uuid == self.load_item_uuid:
+                    if serviceItem.unique_identifier == self.load_item_unique_identifier:
                         serviceItem.edit_id = int(self.load_item_edit_id)
                         serviceItem.temporary_edit = self.load_item_temporary
                     self.addServiceItem(serviceItem, repaint=False)
@@ -756,6 +755,9 @@
             self.loadFile(fileName)
 
     def contextMenu(self, point):
+        """
+        The Right click context menu from the Serviceitem list
+        """
         item = self.serviceManagerList.itemAt(point)
         if item is None:
             return
@@ -777,15 +779,13 @@
         if item.parent() is None:
             self.notesAction.setVisible(True)
         if serviceItem[u'service_item'].is_capable(ItemCapabilities.CanLoop) and  \
-            len(serviceItem[u'service_item'].get_frames()) > 1:
+                len(serviceItem[u'service_item'].get_frames()) > 1:
             self.autoPlaySlidesGroup.menuAction().setVisible(True)
             self.autoPlaySlidesOnce.setChecked(serviceItem[u'service_item'].auto_play_slides_once)
             self.autoPlaySlidesLoop.setChecked(serviceItem[u'service_item'].auto_play_slides_loop)
             self.timedSlideInterval.setChecked(serviceItem[u'service_item'].timed_slide_interval > 0)
             if serviceItem[u'service_item'].timed_slide_interval > 0:
-                delay_suffix = u' '
-                delay_suffix += unicode(serviceItem[u'service_item'].timed_slide_interval)
-                delay_suffix += u' s'
+                delay_suffix = u' %s s' % unicode(serviceItem[u'service_item'].timed_slide_interval)
             else:
                 delay_suffix = u' ...'
             self.timedSlideInterval.setText(translate('OpenLP.ServiceManager', '&Delay between slides') + delay_suffix)
@@ -882,8 +882,8 @@
             timed_slide_interval, 0, 180, 1)
         if ok:
             service_item.timed_slide_interval = timed_slide_interval
-        if service_item.timed_slide_interval <> 0 and not service_item.auto_play_slides_loop\
-            and not service_item.auto_play_slides_once:
+        if service_item.timed_slide_interval != 0 and not service_item.auto_play_slides_loop \
+                and not service_item.auto_play_slides_once:
             service_item.auto_play_slides_loop = True
         elif service_item.timed_slide_interval == 0:
             service_item.auto_play_slides_loop = False
@@ -915,9 +915,9 @@
         Called by the SlideController to request a preview item be made live
         and allows the next preview to be updated if relevant.
         """
-        uuid, row = message.split(u':')
+        unique_identifier, row = message.split(u':')
         for sitem in self.serviceItems:
-            if sitem[u'service_item']._uuid == uuid:
+            if sitem[u'service_item'].unique_identifier == unique_identifier:
                 item = self.serviceManagerList.topLevelItem(sitem[u'order'] - 1)
                 self.serviceManagerList.setCurrentItem(item)
                 self.makeLive(int(row))
@@ -1120,7 +1120,7 @@
                 self.service_has_all_original_files = False
         # Repaint the screen
         self.serviceManagerList.clear()
-        for itemcount, item in enumerate(self.serviceItems):
+        for item_count, item in enumerate(self.serviceItems):
             serviceitem = item[u'service_item']
             treewidgetitem = QtGui.QTreeWidgetItem(self.serviceManagerList)
             if serviceitem.is_valid:
@@ -1169,7 +1169,7 @@
                 text = frame[u'title'].replace(u'\n', u' ')
                 child.setText(0, text[:40])
                 child.setData(0, QtCore.Qt.UserRole, count)
-                if serviceItem == itemcount:
+                if serviceItem == item_count:
                     if item[u'expanded'] and serviceItemChild == count:
                         self.serviceManagerList.setCurrentItem(child)
                     elif serviceItemChild == -1:
@@ -1251,7 +1251,7 @@
         Triggered from plugins to update service items.
         Save the values as they will be used as part of the service load
         """
-        edit_id, self.load_item_uuid, temporary = message.split(u':')
+        edit_id, self.load_item_unique_identifier, temporary = message.split(u':')
         self.load_item_edit_id = int(edit_id)
         self.load_item_temporary = str_to_bool(temporary)
 
@@ -1260,12 +1260,12 @@
         Using the service item passed replace the one with the same edit id
         if found.
         """
-        for itemcount, item in enumerate(self.serviceItems):
+        for item_count, item in enumerate(self.serviceItems):
             if item[u'service_item'].edit_id == newItem.edit_id and item[u'service_item'].name == newItem.name:
                 newItem.render()
                 newItem.merge(item[u'service_item'])
                 item[u'service_item'] = newItem
-                self.repaintServiceList(itemcount + 1, 0)
+                self.repaintServiceList(item_count + 1, 0)
                 self.mainwindow.liveController.replaceServiceManagerItem(newItem)
                 self.setModified()
 
@@ -1322,8 +1322,7 @@
         Receiver.send_message(u'cursor_busy')
         item, child = self.findServiceItem()
         if self.serviceItems[item][u'service_item'].is_valid:
-            self.mainwindow.previewController.addServiceManagerItem(
-                self.serviceItems[item][u'service_item'], child)
+            self.mainwindow.previewController.addServiceManagerItem(self.serviceItems[item][u'service_item'], child)
         else:
             critical_error_message_box(translate('OpenLP.ServiceManager', 'Missing Display Handler'),
                 translate('OpenLP.ServiceManager',
@@ -1408,11 +1407,11 @@
         serviceItem = -1
         serviceItemChild = -1
         for item in items:
-            parentitem = item.parent()
-            if parentitem is None:
+            parent_item = item.parent()
+            if parent_item is None:
                 serviceItem = item.data(0, QtCore.Qt.UserRole)
             else:
-                serviceItem = parentitem.data(0, QtCore.Qt.UserRole)
+                serviceItem = parent_item.data(0, QtCore.Qt.UserRole)
                 serviceItemChild = item.data(0, QtCore.Qt.UserRole)
             # Adjust for zero based arrays.
             serviceItem -= 1
@@ -1461,7 +1460,7 @@
                 if item is None:
                     endpos = len(self.serviceItems)
                 else:
-                    endpos = self._getParentItemData(item) - 1
+                    endpos = self._get_parent_item_data(item) - 1
                 serviceItem = self.serviceItems[startpos]
                 self.serviceItems.remove(serviceItem)
                 self.serviceItems.insert(endpos, serviceItem)
@@ -1474,21 +1473,21 @@
                     self.dropPosition = len(self.serviceItems)
                 else:
                     # we are over something so lets investigate
-                    pos = self._getParentItemData(item) - 1
+                    pos = self._get_parent_item_data(item) - 1
                     serviceItem = self.serviceItems[pos]
                     if (plugin == serviceItem[u'service_item'].name and
                             serviceItem[u'service_item'].is_capable(ItemCapabilities.CanAppend)):
                         action = self.dndMenu.exec_(QtGui.QCursor.pos())
                         # New action required
                         if action == self.newAction:
-                            self.dropPosition = self._getParentItemData(item)
+                            self.dropPosition = self._get_parent_item_data(item)
                         # Append to existing action
                         if action == self.addToAction:
-                            self.dropPosition = self._getParentItemData(item)
+                            self.dropPosition = self._get_parent_item_data(item)
                             item.setSelected(True)
                             replace = True
                     else:
-                        self.dropPosition = self._getParentItemData(item)
+                        self.dropPosition = self._get_parent_item_data(item)
                 Receiver.send_message(u'%s_add_service_item' % plugin, replace)
 
     def updateThemeList(self, theme_list):
@@ -1520,6 +1519,9 @@
         self.regenerateServiceItems()
 
     def onThemeChangeAction(self):
+        """
+        Handles theme change events
+        """
         theme = self.sender().objectName()
         # No object name means that the "Default" theme is supposed to be used.
         if not theme:
@@ -1528,12 +1530,15 @@
         self.serviceItems[item][u'service_item'].update_theme(theme)
         self.regenerateServiceItems(True)
 
-    def _getParentItemData(self, item):
-        parentitem = item.parent()
-        if parentitem is None:
+    def _get_parent_item_data(self, item):
+        """
+        Finds and returns the parent item for any item
+        """
+        parent_item = item.parent()
+        if parent_item is None:
             return item.data(0, QtCore.Qt.UserRole)
         else:
-            return parentitem.data(0, QtCore.Qt.UserRole)
+            return parent_item.data(0, QtCore.Qt.UserRole)
 
     def printServiceOrder(self):
         """

=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py	2013-01-21 19:47:53 +0000
+++ openlp/core/ui/slidecontroller.py	2013-01-23 19:58:22 +0000
@@ -1214,7 +1214,8 @@
         row = self.previewListWidget.currentRow()
         if -1 < row < self.previewListWidget.rowCount():
             if self.serviceItem.from_service:
-                Receiver.send_message('servicemanager_preview_live', u'%s:%s' % (self.serviceItem._uuid, row))
+                Receiver.send_message('servicemanager_preview_live', u'%s:%s' %
+                    (self.serviceItem.unique_identifier, row))
             else:
                 self.parent().liveController.addServiceManagerItem(self.serviceItem, row)
 

=== modified file 'openlp/core/ui/thememanager.py'
--- openlp/core/ui/thememanager.py	2013-01-17 22:14:06 +0000
+++ openlp/core/ui/thememanager.py	2013-01-23 19:58:22 +0000
@@ -474,8 +474,7 @@
             Name of the theme to load from file
         """
         log.debug(u'getthemedata for theme %s', theme_name)
-        xml_file = os.path.join(self.path, unicode(theme_name),
-            unicode(theme_name) + u'.xml')
+        xml_file = os.path.join(self.path, unicode(theme_name), unicode(theme_name) + u'.xml')
         xml = get_text_file_string(xml_file)
         if not xml:
             log.debug(u'No theme data - using default theme')

=== modified file 'openlp/core/utils/__init__.py'
--- openlp/core/utils/__init__.py	2013-01-16 11:29:19 +0000
+++ openlp/core/utils/__init__.py	2013-01-23 19:58:22 +0000
@@ -462,7 +462,7 @@
 
 def format_time(text, local_time):
     """
-    Workaround for Python built-in time formatting fuction time.strftime().
+    Workaround for Python built-in time formatting function time.strftime().
 
     time.strftime() accepts only ascii characters. This function accepts
     unicode string and passes individual % placeholders to time.strftime().

=== modified file 'openlp/plugins/custom/lib/mediaitem.py'
--- openlp/plugins/custom/lib/mediaitem.py	2013-01-11 00:19:11 +0000
+++ openlp/plugins/custom/lib/mediaitem.py	2013-01-23 19:58:22 +0000
@@ -256,7 +256,7 @@
             and_(CustomSlide.title == item.title, CustomSlide.theme_name == item.theme,
                 CustomSlide.credits == item.raw_footer[0][len(item.title) + 1:]))
         if custom:
-            Receiver.send_message(u'service_item_update', u'%s:%s:%s' % (custom.id, item._uuid, False))
+            Receiver.send_message(u'service_item_update', u'%s:%s:%s' % (custom.id, item.unique_identifier, False))
         else:
             if self.add_custom_from_service:
                 self.create_from_service_item(item)
@@ -286,7 +286,7 @@
         self.plugin.manager.save_object(custom)
         self.onSearchTextButtonClicked()
         if item.name.lower() == u'custom':
-            Receiver.send_message(u'service_item_update', u'%s:%s:%s' % (custom.id, item._uuid, False))
+            Receiver.send_message(u'service_item_update', u'%s:%s:%s' % (custom.id, item.unique_identifier, False))
 
     def onClearTextButtonClick(self):
         """

=== modified file 'openlp/plugins/remotes/lib/httpserver.py'
--- openlp/plugins/remotes/lib/httpserver.py	2013-01-10 23:07:48 +0000
+++ openlp/plugins/remotes/lib/httpserver.py	2013-01-23 19:58:22 +0000
@@ -252,17 +252,17 @@
         service_items = []
         service_manager = self.parent.plugin.serviceManager
         if self.parent.current_item:
-            cur_uuid = self.parent.current_item._uuid
+            current_unique_identifier = self.parent.current_item.unique_identifier
         else:
-            cur_uuid = None
+            current_unique_identifier = None
         for item in service_manager.serviceItems:
             service_item = item[u'service_item']
             service_items.append({
-                u'id': unicode(service_item._uuid),
+                u'id': unicode(service_item.unique_identifier),
                 u'title': unicode(service_item.get_display_title()),
                 u'plugin': unicode(service_item.name),
                 u'notes': unicode(service_item.notes),
-                u'selected': (service_item._uuid == cur_uuid)
+                u'selected': (service_item.unique_identifier == current_unique_identifier)
             })
         return service_items
 
@@ -386,9 +386,9 @@
         Poll OpenLP to determine the current slide number and item name.
         """
         result = {
-            u'service': self.parent.plugin.serviceManager.serviceId,
+            u'service': self.parent.plugin.serviceManager.service_id,
             u'slide': self.parent.current_slide or 0,
-            u'item': self.parent.current_item._uuid if self.parent.current_item else u'',
+            u'item': self.parent.current_item.unique_identifier if self.parent.current_item else u'',
             u'twelve':Settings().value(u'remotes/twelve hour'),
             u'blank': self.parent.plugin.liveController.blankScreen.isChecked(),
             u'theme': self.parent.plugin.liveController.themeScreen.isChecked(),
@@ -459,7 +459,7 @@
                     data.append(item)
             json_data = {u'results': {u'slides': data}}
             if current_item:
-                json_data[u'results'][u'item'] = self.parent.current_item._uuid
+                json_data[u'results'][u'item'] = self.parent.current_item.unique_identifier
         else:
             if self.url_params and self.url_params.get(u'data'):
                 try:

=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
--- openlp/plugins/songs/lib/mediaitem.py	2013-01-11 00:19:11 +0000
+++ openlp/plugins/songs/lib/mediaitem.py	2013-01-23 19:58:22 +0000
@@ -538,7 +538,7 @@
             temporary = True
         # Update service with correct song id.
         if editId:
-            Receiver.send_message(u'service_item_update%s:%s:%s' % (editId, item._uuid, temporary))
+            Receiver.send_message(u'service_item_update%s:%s:%s' % (editId, item.unique_identifier, temporary))
 
     def search(self, string, showError):
         """

=== modified file 'tests/functional/openlp_core_lib/test_serviceitem.py'
--- tests/functional/openlp_core_lib/test_serviceitem.py	2013-01-20 20:53:58 +0000
+++ tests/functional/openlp_core_lib/test_serviceitem.py	2013-01-23 19:58:22 +0000
@@ -2,10 +2,15 @@
     Package to test the openlp.core.lib package.
 """
 import os
+import cPickle
 
 from unittest import TestCase
-from mock import MagicMock
-from openlp.core.lib import ServiceItem
+from mock import MagicMock, patch
+from openlp.core.lib import Renderer, Settings
+
+from PyQt4 import QtGui
+
+from openlp.core.lib import ServiceItem, Settings, PluginManager
 
 VERSE = u'The Lord said to {r}Noah{/r}: \n'\
         'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\
@@ -18,11 +23,12 @@
 
 TESTPATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'..', u'resources'))
 
+
 class TestServiceItem(TestCase):
 
     def serviceitem_basic_test(self):
         """
-        Test the Service Item basic test
+        Test the Service Item - basic test
         """
         # GIVEN: A new service item
 
@@ -35,7 +41,7 @@
 
     def serviceitem_add_text_test(self):
         """
-        Test the Service Item add text test
+        Test the Service Item - add text test
         """
         # GIVEN: A new service item
         service_item = ServiceItem(None)
@@ -63,7 +69,7 @@
 
     def serviceitem_add_image_test(self):
         """
-        Test the Service Item add image test
+        Test the Service Item - add image test
         """
         # GIVEN: A new service item and a mocked renderer
         service_item = ServiceItem(None)
@@ -120,7 +126,7 @@
 
     def serviceitem_add_command_test(self):
         """
-        Test the Service Item add command test
+        Test the Service Item - add command test
         """
         # GIVEN: A new service item and a mocked renderer
         service_item = ServiceItem(None)
@@ -161,3 +167,64 @@
 
         # THEN the service item should not be valid
         assert service_item.is_valid is False, u'The service item is not valid'
+
+    def serviceitem_load_custom_from_service_test(self):
+        """
+        Test the Service Item - adding a custom slide from a saved service
+        """
+        # GIVEN: A new service item and a mocked add icon function
+        service_item = ServiceItem(None)
+        mocked_add_icon =  MagicMock()
+        service_item.add_icon = mocked_add_icon
+        #mocked_image_manager = MagicMock()
+        #mocked_theme_contents = MagicMock()
+        #plugin_manager = PluginManager(TESTPATH)
+        #with patch(u'openlp.core.ui.maindisplay.QtGui') as mocked_QTGui, \
+        #    patch(u'openlp.core.ui.maindisplay.QtOpenGL.QGLWidget') as mocked_QTOpenGL,\
+        #    patch(u'openlp.core.ui.maindisplay.QtGui.QAbstractScrollArea') as mocked_QWidgetL:
+        #    mocked_renderer =  Renderer(mocked_image_manager, mocked_theme_contents)
+        mocked_renderer =  MagicMock()
+        service_item.renderer = mocked_renderer
+
+        # WHEN: adding a custom from a saved Service
+        line = self.convert_file_service_item(u'serviceitem_custom1.osd')
+        service_item.set_from_service(line)
+
+        # THEN: We should get back a valid service item
+        assert service_item.is_valid is True, u'The new service item should be valid'
+        assert len(service_item._display_frames) == 0, u'The service item has no display frames'
+        assert len(service_item.capabilities) == 5, u'There are 5 default custom item capabilities'
+        service_item.render(True)
+        assert (service_item.get_display_title()) == u'Test Custom', u'The custom title is correct'
+
+    def serviceitem_load_image_from_service_test(self):
+        """
+        Test the Service Item - adding an image from a saved service
+        """
+        # GIVEN: A new service item and a mocked add icon function
+        service_item = ServiceItem(None)
+        mocked_add_icon =  MagicMock()
+        service_item.add_icon = mocked_add_icon
+        mocked_renderer =  MagicMock()
+        service_item.renderer = mocked_renderer
+
+        # WHEN: adding a custom from a saved Service
+        #with patch(u'openlp.core.lib.settings') as mocked_settings:
+        #    line = self.convert_file_service_item(u'serviceitem_image1.osd')
+        #    service_item.set_from_service(line)
+
+        # THEN: We should get back a valid service item
+        assert service_item.is_valid is True, u'The new service item should be valid'
+        #assert len(service_item._display_frames) == 0, u'The service item has no display frames'
+        #assert len(service_item.capabilities) == 5, u'There are 5 default custom item capabilities'
+        #assert (service_item.get_display_title()) == u'Test Custom', u'The custom title is correct'
+
+    def convert_file_service_item(self, name):
+        service_file = os.path.join(TESTPATH, name)
+        try:
+            open_file = open(service_file, u'r')
+            items = cPickle.load(open_file)
+            first_line = items[0]
+        except:
+            first_line = u''
+        return first_line
\ No newline at end of file

=== modified file 'tests/functional/openlp_core_ui/test_starttimedialog.py'
--- tests/functional/openlp_core_ui/test_starttimedialog.py	2013-01-21 13:04:18 +0000
+++ tests/functional/openlp_core_ui/test_starttimedialog.py	2013-01-23 19:58:22 +0000
@@ -1,13 +1,12 @@
 """
-Package to test the openlp.core.ui package.
+    Package to test the openlp.core.ui package.
 """
 import sys
 from unittest import TestCase
 
-from mock import MagicMock
-
+from mock import MagicMock, patch
 from openlp.core.ui import starttimeform
-from PyQt4 import QtCore, QtGui, QtTest
+from PyQt4 import QtGui, QtTest
 
 class TestStartTimeDialog(TestCase):
 
@@ -48,10 +47,18 @@
         """
         Test StartTimeDialog display initialisation
         """
-        #GIVEN: A service item with with time
+        # GIVEN: A service item with with time
         mocked_serviceitem = MagicMock()
         mocked_serviceitem.start_time = 61
         mocked_serviceitem.end_time = 3701
 
+        # WHEN displaying the UI and pressing enter
         self.form.item = mocked_serviceitem
-        #self.form.exec_()
+        with patch(u'openlp.core.lib.QtGui.QDialog') as MockedQtGuiQDialog:
+            MockedQtGuiQDialog.return_value = True
+            self.form.exec_()
+
+        # THEN the following values are returned
+        self.assertEqual(self.form.hourSpinBox.value(), 1)
+        self.assertEqual(self.form.minuteSpinBox.value(), 1)
+        self.assertEqual(self.form.secondSpinBox.value(), 1)
\ No newline at end of file

=== added file 'tests/resources/serviceitem_custom1.osd'
--- tests/resources/serviceitem_custom1.osd	1970-01-01 00:00:00 +0000
+++ tests/resources/serviceitem_custom1.osd	2013-01-23 19:58:22 +0000
@@ -0,0 +1,96 @@
+(lp1
+(dp2
+Vserviceitem
+p3
+(dp4
+Vheader
+p5
+(dp6
+Vfooter
+p7
+(lp8
+VTest Custom Credits
+p9
+asVaudit
+p10
+V
+sVsearch
+p11
+V
+sVwill_auto_start
+p12
+I00
+sVname
+p13
+Vcustom
+p14
+sVplugin
+p15
+g14
+sVdata
+p16
+V
+sVnotes
+p17
+V
+sVtitle
+p18
+VTest Custom
+p19
+sVfrom_plugin
+p20
+I00
+sVcapabilities
+p21
+(lp22
+I2
+aI1
+aI5
+aI13
+aI8
+asVmedia_length
+p23
+I0
+sVtheme_overwritten
+p24
+I00
+sVtheme
+p25
+NsVxml_version
+p26
+NsVend_time
+p27
+I0
+sVbackground_audio
+p28
+(lp29
+sVtype
+p30
+I1
+sVstart_time
+p31
+I0
+sVicon
+p32
+V:/plugins/plugin_custom.png
+p33
+ssg16
+(lp34
+(dp35
+VverseTag
+p36
+NsVraw_slide
+p37
+VSlide 1
+p38
+sVtitle
+p39
+g38
+sa(dp40
+g36
+Nsg37
+VSlide 2
+p41
+sg39
+g41
+sassa.
\ No newline at end of file

=== added file 'tests/resources/serviceitem_image1.osd'
--- tests/resources/serviceitem_image1.osd	1970-01-01 00:00:00 +0000
+++ tests/resources/serviceitem_image1.osd	2013-01-23 19:58:22 +0000
@@ -0,0 +1,79 @@
+(lp1
+(dp2
+Vserviceitem
+p3
+(dp4
+Vheader
+p5
+(dp6
+Vfooter
+p7
+(lp8
+sVaudit
+p9
+V
+sVsearch
+p10
+V
+sVwill_auto_start
+p11
+I00
+sVname
+p12
+Vimages
+p13
+sVplugin
+p14
+g13
+sVdata
+p15
+V
+sVnotes
+p16
+V
+sVtitle
+p17
+VImages
+p18
+sVfrom_plugin
+p19
+I00
+sVcapabilities
+p20
+(lp21
+I3
+aI1
+aI5
+aI6
+asVmedia_length
+p22
+I0
+sVtheme_overwritten
+p23
+I00
+sVtheme
+p24
+I-1
+sVxml_version
+p25
+NsVend_time
+p26
+I0
+sVbackground_audio
+p27
+(lp28
+sVtype
+p29
+I2
+sVstart_time
+p30
+I0
+sVicon
+p31
+V:/plugins/plugin_images.png
+p32
+ssg15
+(lp33
+VIMG_7453.JPG
+p34
+assa.
\ No newline at end of file