def selectImageFromAlbum(self, destinationDir, supportedMimeTypes, displaySize, randomize, retry=1): result = BaseService.selectImageFromAlbum(self, destinationDir, supportedMimeTypes, displaySize, randomize) if result is None: return None # catch broken urls if result.error is not None and result.source is not None: logging.warning( "broken url detected. You should remove '.../%s' from keywords" % (self.getUrlFilename(result.source))) # catch unsupported mimetypes (can only be done after downloading the image) elif result.error is None and result.mimetype not in supportedMimeTypes: logging.warning( "unsupported mimetype '%s'. You should remove '.../%s' from keywords" % (result.mimetype, self.getUrlFilename(result.source))) else: return result # track broken urls / unsupported mimetypes and display warning message on web interface self.brokenUrls.append(result["source"]) # retry (with another image) if retry > 0: return self.selectImageFromAlbum(destinationDir, supportedMimeTypes, displaySize, randomize, retry=retry - 1) return BaseService.createImageHolder(self).setError( '%s uses broken urls / unsupported images!' % self.SERVICE_NAME)
def __init__(self, configDir, id, name): BaseService.__init__(self, configDir, id, name, needConfig=False, needOAuth=True)
def selectImageFromAlbum(self, destinationDir, supportedMimeTypes, displaySize, randomize): result = BaseService.selectImageFromAlbum(self, destinationDir, supportedMimeTypes, displaySize, randomize) if result is not None: return result if not self.isGooglePhotosEnabled(): return BaseService.createImageHolder(self).setError('"Photos Library API" is not enabled on\nhttps://console.developers.google.com\n\nCheck the Photoframe Wiki for details') else: return BaseService.createImageHolder(self).setError('No (new) images could be found.\nCheck spelling or make sure you have added albums')
def __init__(self, configDir, id, name): BaseService.__init__(self, configDir, id, name, needConfig=False, needOAuth=False) self.brokenUrls = []
def getAlbumInfo(self, path, files): images = [] for filename in files: fullFilename = os.path.join(path, filename) dim = helper.getImageSize(fullFilename) readable = True if dim is None: try: with open(fullFilename, 'rb') as f: f.read(1) logging.warning('File %s has unknown format, skipping', fullFilename) continue except: readable = False if os.path.exists(fullFilename) and readable: item = BaseService.createImageHolder(self) item.setId(self.hashString(fullFilename)) item.setUrl(fullFilename).setSource(fullFilename) item.setMimetype(helper.getMimetype(fullFilename)) item.setDimensions(dim['width'], dim['height']) item.setFilename(filename) images.append(item) else: logging.warning( 'File %s could not be read. Could be USB issue, try rebooting', fullFilename) return images
def selectImageFromAlbum(self, destinationDir, supportedMimeTypes, displaySize, randomize): result = BaseService.selectImageFromAlbum(self, destinationDir, supportedMimeTypes, displaySize, randomize) if result is not None: return result if os.path.exists(self.usbDir): return BaseService.createImageHolder(self).setError( 'No images could be found on storage device "%s"!\n\nPlease place albums inside /photoframe/{album_name} directory and add each {album_name} as keyword.\n\nAlternatively, put images directly inside the "/photoframe/"-directory on your storage device.' % self.device.getName()) else: return BaseService.createImageHolder(self).setError( 'No external storage device detected! Please connect a USB-stick!\n\n Place albums inside /photoframe/{album_name} directory and add each {album_name} as keyword.\n\nAlternatively, put images directly inside the "/photoframe/"-directory on your storage device.' )
def getImagesFor(self, keyword): url = keyword if url in self.brokenUrls: return [] image = BaseService.createImageHolder(self).setId( self.hashString(url)).setUrl(url).setSource(url).allowCache(True) return [image]
def addKeywords(self, keywords): result = BaseService.addKeywords(self, keywords) if result['error'] is None and result['extras'] is not None: k = result['keywords'] extras = self.getExtras() extras[k] = result['extras'] self.setExtras(extras) return result
def validateKeywords(self, keyword): if keyword != 'ALLALBUMS' and keyword != '_PHOTOFRAME_': if keyword not in self.getAllAlbumNames(): return { 'error': 'No such album "%s"' % keyword, 'keywords': keyword } return BaseService.validateKeywords(self, keyword)
def getImagesFor(self, keyword, rawReturn=False): filename = os.path.join(self.getStoragePath(), self.hashString(keyword) + '.json') result = [] if not os.path.exists(filename): # First time, translate keyword into albumid params = self.getQueryForKeyword(keyword) if params is None: logging.error('Unable to create query the keyword "%s"', keyword) return [BaseService.createImageHolder(self).setError('Unable to get photos using keyword "%s"' % keyword)] url = 'https://photoslibrary.googleapis.com/v1/mediaItems:search' maxItems = 1000 # Should be configurable while len(result) < maxItems: data = self.requestUrl(url, data=params, usePost=True) if not data.isSuccess(): logging.warning('Requesting photo failed with status code %d', data.httpcode) logging.warning('More details: ' + repr(data.content)) break else: data = json.loads(data.content) if 'mediaItems' not in data: break logging.debug('Got %d entries, adding it to existing %d entries', len(data['mediaItems']), len(result)) result += data['mediaItems'] if 'nextPageToken' not in data: break params['pageToken'] = data['nextPageToken'] logging.debug('Fetching another result-set for this keyword') if len(result) > 0: with open(filename, 'w') as f: json.dump(result, f) else: logging.error('No result returned for keyword "%s"!', keyword) return [] # Now try loading if os.path.exists(filename): try: with open(filename, 'r') as f: albumdata = json.load(f) except: logging.exception('Failed to decode JSON file, maybe it was corrupted? Size is %d', os.path.getsize(filename)) logging.error('Since file is corrupt, we try to save a copy for later analysis (%s.corrupt)', filename) try: if os.path.exists(filename + '.corrupt'): os.unlink(filename + '.corrupt') os.rename(filename, filename + '.corrupt') except: logging.exception('Failed to save copy of corrupt file, deleting instead') os.unlink(filename) albumdata = None if rawReturn: return albumdata return self.parseAlbumInfo(albumdata, keyword)
def getMessages(self): msgs = BaseService.getMessages(self) msgs.append({ 'level': 'WARNING', 'message': 'This provider will cease to function January 1st, 2019. Please use GooglePhotos. For more details, see photoframe wiki', 'link': 'https://github.com/mrworf/photoframe/wiki/PicasaWeb-API-ceases-to-work-January-1st,-2019' }) return msgs
def getMessages(self): msgs = BaseService.getMessages(self) msgs.append({ 'level': 'ERROR', 'message': 'This provider is no longer supported by Google. Please use GooglePhotos. For more details, see photoframe wiki', 'link': 'https://github.com/mrworf/photoframe/wiki/PicasaWeb-API-ceases-to-work-January-1st,-2019' }) return msgs
def updateState(self): self.subState = None if not os.path.exists(self.baseDir): if not self.mountStorageDevice(): self._CURRENT_STATE = BaseService.STATE_NO_IMAGES self.subState = USB_Photos.SUBSTATE_NOT_CONNECTED return self._CURRENT_STATE if len(self.getAllAlbumNames()) == 0 and len(self.getBaseDirImages()) == 0: self._CURRENT_STATE = BaseService.STATE_NO_IMAGES return self._CURRENT_STATE return BaseService.updateState(self)
def parseAlbumInfo(self, data): # parse GooglePhoto specific keys into a format that the base service can understand if data is None: return None parsedImages = [] for entry in data: item = BaseService.createImageHolder(self) item.setId(entry['id']) item.setSource(entry['productUrl']).setMimetype(entry['mimeType']) item.setDimensions(entry['mediaMetadata']['width'], entry['mediaMetadata']['height']) item.allowCache(True) parsedImages.append(item) return parsedImages
def getAlbumInfo(self, path, files): images = [] for filename in files: fullFilename = os.path.join(path, filename) item = BaseService.createImageHolder(self) item.setId(self.hashString(fullFilename)) item.setUrl(fullFilename).setSource(fullFilename) item.setMimetype(helper.getMimetype(fullFilename)) dim = helper.getImageSize(fullFilename) item.setDimensions(dim['width'], dim['height']) item.setFilename(filename) images.append(item) return images
def removeKeywords(self, index): # Override since we need to delete our private data keys = self.getKeywords() if index < 0 or index >= len(keys): return keywords = keys[index].upper().lower().strip() filename = os.path.join(self.getStoragePath(), self.hashString(keywords) + '.json') if os.path.exists(filename): os.unlink(filename) if BaseService.removeKeywords(self, index): # Remove any extras extras = self.getExtras() if keywords in extras: del extras[keywords] self.setExtras(extras) return True else: return False
def validateKeywords(self, keywords): tst = BaseService.validateKeywords(self, keywords) if tst["error"] is not None: return tst # Remove quotes around keyword if keywords[0] == '"' and keywords[-1] == '"': keywords = keywords[1:-1] keywords = keywords.upper().lower().strip() # No error in input, resolve album now and provide it as extra data albumId = None if keywords != 'latest': albumId = self.translateKeywordToId(keywords) if albumId is None: return {'error':'No such album "%s"' % keywords, 'keywords' : keywords} return {'error':None, 'keywords':keywords, 'extras' : albumId}
def getMessages(self): # display a message indicating which storage device is being used or an error messing if no suitable storage device could be found if os.path.exists(self.baseDir): msgs = [ { 'level': 'SUCCESS', 'message': 'Storage device "%s" is mounted to "%s"' % (self.device if self.device is not None else "unknown", self.usbDir), 'link': None } ] msgs.extend(BaseService.getMessages(self)) else: msgs = [ { 'level': 'ERROR', 'message': 'No storage device could be found that contains the "/photoframe/"-directory! Try to reboot or manually mount the desired storage device to "%s"' % self.usbDir, 'link': None } ] return msgs
def parseAlbumInfo(self, data, keyword): # parse GooglePhoto specific keys into a format that the base service can understand if data is None: return None parsedImages = [] for entry in data: if entry['mimeType'] not in helper.getSupportedTypes(): continue try: item = BaseService.createImageHolder(self) item.setId(entry['id']) item.setSource(entry['productUrl']).setMimetype( entry['mimeType']) item.setDimensions(entry['mediaMetadata']['width'], entry['mediaMetadata']['height']) item.allowCache(True) item.setContentProvider(self) item.setContentSource(keyword) parsedImages.append(item) except: logging.exception('Failed due to:') logging.debug('Entry: %s', repr(entry)) return parsedImages
def memoryForget(self, keywords=None, forgetHistory=False): # give broken URLs another try (server may have been temporarily unavailable) self.brokenUrls = [] return BaseService.memoryForget(self, keywords=keywords, forgetHistory=forgetHistory)
def removeKeywords(self, index): url = self.getKeywords()[index] result = BaseService.removeKeywords(self, index) if result and url in self.brokenUrls: self.brokenUrls.remove(url) return result
def validateKeywords(self, keywords): # Catches most invalid URLs if not helper.isValidUrl(keywords): return {'error': 'URL appears to be invalid', 'keywords': keywords} return BaseService.validateKeywords(self, keywords)
def getExtras(self): # Normalize result = BaseService.getExtras(self) if result is None: return {} return result