def _lib_folders(section): section_dir = g.py2_decode( xbmc.translatePath( makeLegalFilename('/'.join([library_path(), section])))) return [ g.py2_decode( makeLegalFilename('/'.join([section_dir, folder.decode('utf-8')]))) for folder in xbmcvfs.listdir(section_dir)[0] ]
def _get_status_icon(): enabled = CACHE.get(f"{ADDONID}.service_enabled") daylight = CACHE.get(f"{ADDONID}.daylight") daylight_disable = ADDON.getSettingBool("daylightDisable") # xbmc.log("[script.service.hue] Current status: {}".format(daylight_disable)) if daylight and daylight_disable: return xbmcvfs.makeLegalFilename(ADDONPATH + "resources/icons/daylight.png") # Disabled by Daylight elif enabled: return xbmcvfs.makeLegalFilename(ADDONPATH + "resources/icons/enabled.png") # Enabled return xbmcvfs.makeLegalFilename(ADDONPATH + "resources/icons/disabled.png") # Disabled
def make_legal_filename(filename, prefix='', suffix=''): """Returns a legal filename, from an arbitrary string input, as a string""" filename = ''.join((prefix, filename, suffix)) try: filename = xbmcvfs.makeLegalFilename(filename) except AttributeError: xbmcvfs.makeLegalFilename = xbmc.makeLegalFilename filename = xbmcvfs.makeLegalFilename(filename) if filename.endswith('/'): filename = filename[:-1] return filename
def _get_item(mediatype, filename): # To ensure compatibility with previously exported items, # make the filename legal fname = makeLegalFilename(filename) untranslated_path = os.path.dirname(g.py2_decode(fname)) translated_path = os.path.dirname(g.py2_decode(xbmc.translatePath(fname))) shortname = os.path.basename(g.py2_decode(xbmc.translatePath(fname))) # We get the data from Kodi library using filters. # This is much faster than loading all episodes in memory # First build the path filter, we may have to search in both special and translated path path_filter = {'field': 'path', 'operator': 'startswith', 'value': translated_path} \ if fname[:10] != 'special://' \ else {'or': [ {'field': 'path', 'operator': 'startswith', 'value': translated_path}, {'field': 'path', 'operator': 'startswith', 'value': untranslated_path} ]} # Now build the all request and call the json-rpc function through common.get_library_items library_item = common.get_library_items( mediatype, { 'and': [ path_filter, { 'field': 'filename', 'operator': 'is', 'value': shortname } ] })[0] if not library_item: raise ItemNotFound return common.get_library_item_details(mediatype, library_item[mediatype + 'id'])
def makeLegalFilename(filename): filename = xbmcvfs.makeLegalFilename(filename) if filename[-1:] == os.path.sep or filename[-1:] == "/": filename = filename[:-1] return filename
def scan_library(path=''): """ Start a Kodi library scanning in a specified folder to find new items :param path: Update only the library elements in the specified path (fast processing) """ method = 'VideoLibrary.Scan' params = {'directory': xbmcvfs.makeLegalFilename(xbmcvfs.translatePath(path))} return json_rpc(method, params)
def start_update_kodi_library(self): if not self.scan_in_progress and not self.clean_in_progress: common.debug('Start Kodi library scan') self.scan_in_progress = True # Set as in progress (avoid wait "started" callback it comes late) self.scan_awaiting = False # Update only the library elements in the add-on export folder # for faster processing (on Kodi 18.x) with a large library common.scan_library(makeLegalFilename(xbmc.translatePath(get_library_path()))) else: self.scan_awaiting = True
def export_item(item_task, library_home): """Create strm file for an item and add it to the library""" # Paths must be legal to ensure NFS compatibility destination_folder = g.py2_decode( makeLegalFilename('/'.join( [library_home, item_task['section'], item_task['destination']]))) _create_destination_folder(destination_folder) if item_task['is_strm']: export_filename = g.py2_decode( makeLegalFilename('/'.join( [destination_folder, item_task['filename'] + '.strm']))) _add_to_library(item_task['videoid'], export_filename, (item_task['nfo_data'] is not None)) _write_strm_file(item_task, export_filename) if item_task['nfo_data'] is not None: nfo_filename = g.py2_decode( makeLegalFilename('/'.join( [destination_folder, item_task['filename'] + '.nfo']))) _write_nfo_file(item_task['nfo_data'], nfo_filename) common.debug('Exported {}', item_task['title'])
def update_kodi_library(self, data=None): # pylint: disable=unused-argument # Update only the elements in the addon export folder for faster processing with a large library (on Kodi 18.x) # If a scan is already in progress, the scan is delayed until onScanFinished event if not self.scan_in_progress: common.debug( 'Kodi library update requested from library auto-update') self.scan_awaiting = False common.scan_library( makeLegalFilename( xbmc.translatePath(kodi_library.library_path()))) else: self.scan_awaiting = True
def clean_library(show_dialog=True, path=''): """ Start a Kodi library cleaning to remove non-existing items :param show_dialog: True a progress dialog is shown :param path: Clean only the library elements in the specified path (fast processing) """ method = 'VideoLibrary.Clean' params = {'content': 'video', 'showdialogs': show_dialog} if path: params['directory'] = xbmcvfs.makeLegalFilename(xbmcvfs.translatePath(path)) return json_rpc(method, params)
def build_menu(base_url, addon_handle): status_item = ListItem(_("Hue Status: ") + _get_status()) status_icon = _get_status_icon() if status_icon: status_item.setArt({"icon": status_icon}) xbmc.log(f"[script.service.hue] status_icon: {status_icon}") settings_item = ListItem(_("Settings")) settings_item.setArt({"icon": xbmcvfs.makeLegalFilename(ADDONPATH + "resources/icons/settings.png")}) xbmcplugin.addDirectoryItem(addon_handle, base_url + "/actions?light_group_id=1&action=menu", ListItem(_("Video Actions")), True) xbmcplugin.addDirectoryItem(addon_handle, base_url + "/actions?light_group_id=2&action=menu", ListItem(_("Audio Actions")), True) xbmcplugin.addDirectoryItem(addon_handle, base_url + "?toggle", status_item) xbmcplugin.addDirectoryItem(addon_handle, base_url + "?settings", settings_item) xbmcplugin.endOfDirectory(handle=addon_handle, cacheToDisc=False)
def _get_item_details_from_kodi(mediatype, file_path): """Get a Kodi library item with details (from Kodi database) by searching with the file path""" # To ensure compatibility with previously exported items, make the filename legal file_path = makeLegalFilename(file_path) dir_path = os.path.dirname(G.py2_decode(xbmc.translatePath(file_path))) filename = os.path.basename(G.py2_decode(xbmc.translatePath(file_path))) # We get the data from Kodi library using filters, this is much faster than loading all episodes in memory. if file_path[:10] == 'special://': # If the path is special, search with real directory path and also special path special_dir_path = os.path.dirname(G.py2_decode(file_path)) path_filter = { 'or': [{ 'field': 'path', 'operator': 'startswith', 'value': dir_path }, { 'field': 'path', 'operator': 'startswith', 'value': special_dir_path }] } else: path_filter = { 'field': 'path', 'operator': 'startswith', 'value': dir_path } # Now build the all request and call the json-rpc function through get_library_items library_items = get_library_items( mediatype, { 'and': [ path_filter, { 'field': 'filename', 'operator': 'is', 'value': filename } ] }) if not library_items: raise ItemNotFound return get_library_item_details(mediatype, library_items[0][mediatype + 'id'])
def clean_library(show_dialog=False, path=''): method = 'VideoLibrary.Clean' params = {'content': 'video', 'showdialogs': show_dialog} if path: params['directory'] = xbmcvfs.makeLegalFilename( xbmcvfs.translatePath(path)) while xbmc.getCondVisibility( 'Library.IsScanningVideo') or xbmc.getCondVisibility( 'Library.IsScanningMusic'): xbmc.Monitor().waitForAbort(1) result = json_rpc(method, params) while xbmc.getCondVisibility( 'Library.IsScanningVideo') or xbmc.getCondVisibility( 'Library.IsScanningMusic'): xbmc.Monitor().waitForAbort(1) return result
def remove_item(item_task, library_home=None): """Remove an item from the library and delete if from disk""" # pylint: disable=unused-argument, broad-except common.info('Removing {} from library', item_task['title']) exported_filename = g.py2_decode(xbmc.translatePath(item_task['filepath'])) videoid = item_task['videoid'] common.debug('VideoId: {}', videoid) try: parent_folder = g.py2_decode( xbmc.translatePath(os.path.dirname(exported_filename))) if xbmcvfs.exists(exported_filename): xbmcvfs.delete(exported_filename) else: common.warn('Cannot delete {}, file does not exist', exported_filename) # Remove the NFO files if exists nfo_file = os.path.splitext(exported_filename)[0] + '.nfo' if xbmcvfs.exists(nfo_file): xbmcvfs.delete(nfo_file) dirs, files = xbmcvfs.listdir(parent_folder) tvshow_nfo_file = g.py2_decode( makeLegalFilename('/'.join([parent_folder, 'tvshow.nfo']))) # Remove tvshow_nfo_file only when is the last file # (users have the option of removing even single seasons) if xbmcvfs.exists(tvshow_nfo_file) and not dirs and len(files) == 1: xbmcvfs.delete(tvshow_nfo_file) # Delete parent folder xbmcvfs.rmdir(parent_folder) # Delete parent folder when empty if not dirs and not files: xbmcvfs.rmdir(parent_folder) _remove_videoid_from_db(videoid) except ItemNotFound: common.warn('The video with id {} not exists in the database', videoid) except Exception as exc: import traceback common.error(g.py2_decode(traceback.format_exc(), 'latin-1')) ui.show_addon_error_info(exc)
def purge(): """Purge all items exported to Kodi library and delete internal library database""" common.info('Purging internal database and kodi library') for videoid_value in g.SHARED_DB.get_movies_id_list(): videoid = common.VideoId.from_path( [common.VideoId.MOVIE, videoid_value]) execute_library_tasks(videoid, [remove_item], common.get_local_string(30030)) for videoid_value in g.SHARED_DB.get_tvshows_id_list(): videoid = common.VideoId.from_path( [common.VideoId.SHOW, videoid_value]) execute_library_tasks(videoid, [remove_item], common.get_local_string(30030)) # If for some reason such as improper use of the add-on, unexpected error or other # has caused inconsistencies with the contents of the database or stored files, # make sure that everything is removed g.SHARED_DB.purge_library() for folder_name in [FOLDER_MOVIES, FOLDER_TV]: section_dir = xbmc.translatePath( makeLegalFilename('/'.join([library_path(), folder_name]))) common.delete_folder_contents(section_dir, delete_subfolders=True)
def get_previously_exported_items(): """Return a list of movie or tvshow VideoIds for items that were exported in the old storage format""" result = [] videoid_pattern = re.compile('video_id=(\\d+)') for folder in _lib_folders(FOLDER_MOVIES) + _lib_folders(FOLDER_TV): for filename in xbmcvfs.listdir(folder)[1]: filepath = g.py2_decode( makeLegalFilename('/'.join([folder, filename]))) if filepath.endswith('.strm'): common.debug('Trying to migrate {}', filepath) try: # Only get a VideoId from the first file in each folder. # For shows, all episodes will result in the same VideoId # and movies only contain one file result.append(_get_root_videoid(filepath, videoid_pattern)) except MetadataNotAvailable: common.warn('Metadata not available, item skipped') except (AttributeError, IndexError): common.warn('Item does not conform to old format') break return result
def makeLegalFilename(path, trans_none=''): """ Kodi 19: xbmc.makeLegalFilename is deprecated and might be removed in future kodi versions. Please use xbmcvfs.makeLegalFilename instead. @param path: cadena a convertir platform specific @type path: str @rtype: str @return: devuelve la cadena con el path ajustado """ if not path or not isinstance(path, (unicode, basestring, bytes)): if path is None: path = trans_none return path if PY3 and xbmc_vfs: if PY3 and isinstance(path, bytes): path = path.decode(fs_encoding) path = xbmcvfs.makeLegalFilename(path) if isinstance(path, bytes): path = path.decode(fs_encoding) elif KODI: path = xbmc.makeLegalFilename(path) return path
def make_legal_filename(path): from resources.lib.system import SYSTEM_VERSION if SYSTEM_VERSION > 18: return xbmcvfs.makeLegalFilename(path) return xbmc.makeLegalFilename(path)
def recycle(source, dest_folder): """Move a file to a new destination. Will create destination if it does not exist. Example: result = recycle(a, b) :type source: unicode :param source: the source path (absolute) :type dest_folder: unicode :param dest_folder: the destination path (absolute) :rtype: bool :return: True if (all stacked) files were moved, False otherwise """ paths = split_stack(source) files_moved_successfully = 0 dest_folder = xbmcvfs.makeLegalFilename(dest_folder) for p in paths: debug(f"Attempting to move {p} to {dest_folder}.") if xbmcvfs.exists(p): if not xbmcvfs.exists(dest_folder): if xbmcvfs.mkdirs(dest_folder): debug(f"Created destination {dest_folder}.") else: debug(f"Destination {dest_folder} could not be created.", xbmc.LOGERROR) return False new_path = os.path.join(dest_folder, os.path.basename(p)) if xbmcvfs.exists(new_path): debug("A file with the same name already exists in the holding folder. Checking file sizes.") existing_file = xbmcvfs.File(new_path) file_to_move = xbmcvfs.File(p) if file_to_move.size() > existing_file.size(): debug("This file is larger than the existing file. Replacing it with this one.") existing_file.close() file_to_move.close() if bool(xbmcvfs.delete(new_path) and bool(xbmcvfs.rename(p, new_path))): files_moved_successfully += 1 else: return False else: debug("This file isn't larger than the existing file. Deleting it instead of moving.") existing_file.close() file_to_move.close() if bool(xbmcvfs.delete(p)): files_moved_successfully += 1 else: return False else: debug(f"Moving {p} to {new_path}.") move_success = bool(xbmcvfs.rename(p, new_path)) copy_success, delete_success = False, False if not move_success: debug("Move failed, falling back to copy and delete.", xbmc.LOGWARNING) copy_success = bool(xbmcvfs.copy(p, new_path)) if copy_success: debug("Copied successfully, attempting delete of source file.") delete_success = bool(xbmcvfs.delete(p)) if not delete_success: debug("Could not remove source file. Please remove the file manually.", xbmc.LOGWARNING) else: debug("Copying failed, please make sure you have appropriate permissions.", xbmc.LOGFATAL) return False if move_success or (copy_success and delete_success): files_moved_successfully += 1 else: debug(f"File {p} is no longer available.", xbmc.LOGWARNING) return len(paths) == files_moved_successfully
def join_folders_paths(*args): """Join multiple folder paths in a safe way""" # Avoid the use of os.path.join, in some cases with special chars like % break the path return G.py2_decode(makeLegalFilename('/'.join(args)))