def authorise(self, level): with Busy(): if self.session.permitted(level): return True # do full authentication procedure auth_url = self.session.get_auth_url(level) auth_code = self.auth_dialog(auth_url) if not auth_code: return False with Busy(): return self.session.get_access_token(auth_code, level)
def search(self, search_string=None): if not search_string: search_string = self.edit_box.lineEdit().text() self.edit_box.clearEditText() if not search_string: return self.search_string = search_string self.clear_search() params = { 'q': search_string, 'format': 'json', 'polygon': '0', 'addressdetails': '0', } if 'bounds' in self.map_status: bounds = self.map_status['bounds'] params['viewbox'] = '{:.8f},{:.8f},{:.8f},{:.8f}'.format( bounds[3], bounds[0], bounds[1], bounds[2]) headers = {'user-agent': 'Photini/' + __version__} with Busy(): try: rsp = requests.get('http://nominatim.openstreetmap.org/search', params=params, headers=headers) except Exception as ex: self.logger.error(str(ex)) return if rsp.status_code >= 400: return for result in rsp.json(): self.search_result(result['boundingbox'][0], result['boundingbox'][3], result['boundingbox'][1], result['boundingbox'][2], result['display_name'])
def query(self, params): params['key'] = self.api_key params['abbrv'] = '1' params['no_annotations'] = '1' lang, encoding = locale.getdefaultlocale() if lang: params['language'] = lang with Busy(): self.rate_limit() try: rsp = requests.get( 'https://api.opencagedata.com/geocode/v1/json', params=params, timeout=5) except Exception as ex: logger.error(str(ex)) return [] if rsp.status_code >= 400: logger.error('Search error %d', rsp.status_code) return [] rsp = rsp.json() status = rsp['status'] if status['code'] != 200: logger.error('Search error %d: %s', status['code'], status['message']) return [] if rsp['total_results'] < 1: logger.error('No results found') return [] rate = rsp['rate'] self.block_timer.setInterval(5000 * rate['limit'] // max(rate['remaining'], 1)) return rsp['results']
def copy_selected(self): if self.import_in_progress: # user has clicked while import is still cancelling self.copy_button.setChecked(False) return self.import_in_progress = True copy_list = [] for item in self.file_list_widget.selectedItems(): name = item.data(Qt.UserRole) copy_list.append(self.file_data[name]) last_item = None, datetime.min with Busy(): for item in self.source.copy_files(copy_list): if not item: self._fail() break if self.abort_copy(): break self.image_list.open_file(item['dest_path']) if self.abort_copy(): break if last_item[1] < item['timestamp']: last_item = item['dest_path'], item['timestamp'] QtCore.QCoreApplication.flush() if last_item[0]: self.config_store.set(self.config_section, 'last_transfer', last_item[1].isoformat(' ')) self.image_list.done_opening(last_item[0]) self.show_file_list() self.copy_button.setChecked(False) self.import_in_progress = False
def do_search(self, query, params={}): self.disable_search() params['key'] = self.api_key params['q'] = query params['abbrv'] = '1' params['no_annotations'] = '1' lang, encoding = locale.getdefaultlocale() if lang: params['language'] = lang with Busy(): try: rsp = requests.get( 'https://api.opencagedata.com/geocode/v1/json', params=params) except Exception as ex: self.logger.error(str(ex)) return None if rsp.status_code >= 400: self.logger.error('Search error %d', rsp.status_code) return None rsp = rsp.json() status = rsp['status'] if status['code'] != 200: self.logger.error( 'Search error %d: %s', status['code'], status['message']) return None rate = rsp['rate'] self.block_timer.setInterval( 10000 * rate['limit'] // max(rate['remaining'], 1)) return rsp
def delete_album(self): album = self.current_album if int(album.numphotos.text) > 0: if QtWidgets.QMessageBox.question( self, self.tr('Delete album'), self.tr("""Are you sure you want to delete the album "{0}"? Doing so will remove the album and its photos from all Google products.""" ).format(album.title.text), QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel ) == QtWidgets.QMessageBox.Cancel: return with Busy(): self.timer.stop() self.album_changed = False if not self.session.permitted('write'): self.refresh() return self.current_album = None self.upload_config.show_album(None) QtWidgets.QApplication.processEvents() self.session.delete_album(album) self.upload_config.albums.removeItem( self.upload_config.albums.findData(album.id.text)) self.set_current_album()
def refresh(self, force=False): with Busy(): self.connected = (self.user_connect.isChecked() and self.session.permitted('read')) if self.connected: self.user_connect.setText( translate('PhotiniUploader', 'Log out')) if force: # load_user_data can be slow, so only do it when forced try: self.load_user_data() except Exception as ex: logger.error(ex) self.connected = False if not self.connected: self.user_connect.setText( translate('PhotiniUploader', 'Log in')) # clearing user data is quick so do it anyway self.load_user_data() self.user_connect.setChecked(self.connected) self.upload_config.setEnabled(self.connected and not self.upload_worker) self.user_connect.setEnabled(not self.upload_worker) # enable or disable upload button self.new_selection(self.image_list.get_selected_images())
def do_bing_geocode(self, query='', params={}): self.disable_search() params['key'] = self.map_status['session_id'] url = 'http://dev.virtualearth.net/REST/v1/Locations' if query: url += '/' + query with Busy(): try: rsp = requests.get(url, params=params, timeout=5) except Exception as ex: logger.error(str(ex)) return [] if rsp.status_code >= 400: logger.error('Search error %d', rsp.status_code) return [] if rsp.headers['X-MS-BM-WS-INFO'] == '1': logger.error('Server overload') else: # re-enable search immediately rather than after timeout self.enable_search() rsp = rsp.json() if rsp['statusCode'] != 200: logger.error('Search error %d: %s', rsp['statusCode'], rsp['statusDescription']) return [] resource_sets = rsp['resourceSets'] if not resource_sets: logger.error('No results found') return [] return resource_sets
def sync_metadata(self): # make list of known photo ids photo_ids = {} unknowns = [] for image in self.image_list.get_selected_images(): for keyword in image.metadata.keywords or []: name_pred, sep, value = keyword.partition('=') if name_pred == ID_TAG: photo_ids[value] = image break else: unknowns.append(image) # try to find unknowns on Flickr for image in unknowns: for photo in self._find_on_flickr(image): if photo['id'] in photo_ids: continue match = self._find_local(photo, unknowns) if match: match.metadata.keywords = list( match.metadata.keywords or []) + [ '{}={}'.format(ID_TAG, photo['id'])] photo_ids[photo['id']] = match unknowns.remove(match) # merge Flickr metadata into file with Busy(): for photo_id, image in photo_ids.items(): self._merge_metadata(photo_id, image)
def _save_files(self, images=[]): self._flush_editing() if_mode = self.app.config_store.get('files', 'image', True) sc_mode = self.app.config_store.get('files', 'sidecar', 'auto') force_iptc = self.app.config_store.get('files', 'force_iptc', False) keep_time = self.app.config_store.get('files', 'preserve_timestamps', 'now') if isinstance(keep_time, bool): # old config format keep_time = ('now', 'keep')[keep_time] if not images: images = self.images with Busy(): for image in images: if keep_time == 'taken' and image.metadata.date_taken: file_times = ( image.file_times[0], image.metadata.date_taken['datetime'].timestamp()) elif keep_time == 'keep': file_times = image.file_times else: file_times = None image.metadata.save(if_mode=if_mode, sc_mode=sc_mode, force_iptc=force_iptc, file_times=file_times) unsaved = any([image.metadata.changed() for image in self.images]) self.new_metadata.emit(unsaved)
def show_album(self, album): if album: self.albums.setCurrentIndex(self.albums.findData(album.id.text)) self.widgets['description'].set_value(album.summary.text) self.widgets['location'].setText(album.location.text) self.widgets['access'].setCurrentIndex( self.widgets['access'].findData(album.access.text)) self.widgets['timestamp'].setDateTime( QtCore.QDateTime.fromTime_t(int(album.timestamp.text) // 1000)) if album.group.thumbnail is not None: QtWidgets.QApplication.processEvents() with Busy(): url = album.group.thumbnail[0].get('url') image = QtGui.QPixmap(160, 160) try: image.loadFromData(urlopen(url).read()) except HTTPError as ex: paint = QtGui.QPainter(image) paint.fillRect(image.rect(), Qt.black) paint.setPen(Qt.white) paint.drawText(image.rect(), Qt.AlignLeft | Qt.TextWordWrap, str(ex)) paint.end() self.album_thumb.setPixmap(image) else: self.widgets['description'].set_value(None) self.widgets['location'].clear() self.widgets['timestamp'].clear() self.album_thumb.clear()
def do_google_geocode(self, params): self.disable_search() params['key'] = self.api_key lang, encoding = locale.getdefaultlocale() if lang: params['language'] = lang url = 'https://maps.googleapis.com/maps/api/geocode/json' with Busy(): try: rsp = requests.get(url, params=params, timeout=5) except Exception as ex: logger.error(str(ex)) return [] if rsp.status_code >= 400: logger.error('Search error %d', rsp.status_code) return [] self.enable_search() rsp = rsp.json() if rsp['status'] != 'OK': if 'error_message' in rsp: logger.error('Search error: %s: %s', rsp['status'], rsp['error_message']) else: logger.error('Search error: %s', rsp['status']) return [] results = rsp['results'] if not results: logger.error('No results found') return [] return results
def _save_files(self, images=[]): if_mode = eval(self.app.config_store.get('files', 'image', 'True')) sc_mode = self.app.config_store.get('files', 'sidecar', 'auto') force_iptc = eval( self.app.config_store.get('files', 'force_iptc', 'False')) keep_time = eval( self.app.config_store.get('files', 'preserve_timestamps', 'False')) if not images: images = self.images with Busy(): for image in images: if keep_time: file_times = image.file_times else: file_times = None image.metadata.save(if_mode=if_mode, sc_mode=sc_mode, force_iptc=force_iptc, file_times=file_times) unsaved = False for image in self.images: if image.metadata.changed(): unsaved = True break self.new_metadata.emit(unsaved)
def query(self, params, url): params['key'] = self.api_key with Busy(): self.rate_limit() try: rsp = requests.get(url, params=params, timeout=5) except Exception as ex: logger.error(str(ex)) return [] if rsp.status_code >= 400: logger.error('Search error %d', rsp.status_code) return [] if rsp.headers['X-MS-BM-WS-INFO'] == '1': logger.error( translate('MapTabBing', 'Server overload, please try again')) self.block_timer.start(5000) rsp = rsp.json() if rsp['statusCode'] != 200: logger.error('Search error %d: %s', rsp['statusCode'], rsp['statusDescription']) return [] resource_sets = rsp['resourceSets'] if not resource_sets: logger.error('No results found') return [] return resource_sets
def save_changes(self): self.timer.stop() if not self.album_changed: return self.album_changed = False with Busy(): self.session.edit_node(self.current_album) self.set_current_album(self.current_album.id.text)
def fix_missing_thumbs(self): with Busy(): for image in self.get_images(): thumb = image.metadata.thumbnail if not thumb or not thumb['image']: if image.regenerate_thumbnail(): image.load_thumbnail() self.image_list_changed.emit()
def list_files(self): file_data = {} if self.source: with Busy(): file_data = self.source.get_file_data() if file_data is None: self._fail() return self._new_file_list(file_data)
def log_in(self, do_auth=True): with DisableWidget(self.user_connect): with Busy(): connect = self.session.connect() if connect is None: # can't reach server return if do_auth and not connect: self.authorise()
def select_album(self, album_id): with Busy(): self.save_changes() if not self.session.permitted('read'): self.refresh() return self.current_album = None self.upload_config.show_album(None) QtWidgets.QApplication.processEvents() self.set_current_album(album_id)
def get_address(self): lat, lon = self.coords.get_value().split(',') params = { 'lat': lat.strip(), 'lon': lon.strip(), 'zoom': '18', 'format': 'json', 'addressdetails': '1', } headers = {'user-agent': 'Photini/' + __version__} with Busy(): try: rsp = requests.get( 'http://nominatim.openstreetmap.org/reverse', params=params, headers=headers) except Exception as ex: self.logger.error(str(ex)) return if rsp.status_code >= 400: return rsp = rsp.json() if 'error' in rsp: self.logger.error(rsp['error']) return address = rsp['address'] location = [] for iptc_key, osm_keys in (('world_region', ()), ('country_code', ('country_code', )), ('country_name', ('country', )), ('province_state', ('region', 'county', 'state_district', 'state')), ('city', ('hamlet', 'locality', 'neighbourhood', 'village', 'suburb', 'town', 'city_district', 'city')), ('sublocation', ('building', 'house_number', 'footway', 'pedestrian', 'road'))): element = [] for key in osm_keys: if key not in address: continue if address[key] not in element: element.append(address[key]) del (address[key]) location.append(', '.join(element)) # put remaining keys in sublocation for key in address: if key in ('postcode', ): continue location[-1] = '{}: {}, {}'.format(key, address[key], location[-1]) self.set_location_taken(*location)
def save_files(self): if_mode = eval(self.app.config_store.get('files', 'image', 'True')) sc_mode = self.app.config_store.get('files', 'sidecar', 'auto') force_iptc = eval( self.app.config_store.get('files', 'force_iptc', 'False')) unsaved = False with Busy(): for image in self.images: image.metadata.save(if_mode, sc_mode, force_iptc) unsaved = unsaved or image.metadata.changed() self.new_metadata.emit(unsaved)
def connection_changed(self, connected): if connected: with Busy(): self.show_user(*self.session.get_user()) self.show_album_list(self.session.get_albums()) else: self.show_user(None, None) self.show_album_list([]) self.user_connect.set_checked(connected) self.upload_config.setEnabled(connected and not self.upload_worker) self.user_connect.setEnabled(not self.upload_worker) self.enable_upload_button()
def select_album(self, index): if not self.authorise('read'): self.refresh(force=True) return album_id = self.upload_config.widgets['album_choose'].itemData(index) if album_id == 'me': self.upload_config.show_album({}, None) return with Busy(): album, picture = self.session.get_album( album_id, 'cover_photo,description,location,name') self.upload_config.show_album(album, picture)
def _sort_thumbnails(self): sort_date = self.sort_date.isChecked() self.app.config_store.set('controls', 'sort_date', str(sort_date)) with Busy(): if sort_date: self.images.sort(key=self._date_key) else: self.images.sort(key=lambda x: x.path) for image in self.images: self.show_thumbnail(image, False) if self.last_selected: self.app.processEvents() self.scroll_area.ensureWidgetVisible(self.last_selected) self.image_list_changed.emit()
def find_photos(self, min_taken_date, max_taken_date): # search Flickr page = 1 while True: with Busy(): rsp = self.api_call( 'flickr.people.getPhotos', user_id='me', page=page, extras='date_taken,url_t', min_taken_date=min_taken_date.strftime('%Y-%m-%d %H:%M:%S'), max_taken_date=max_taken_date.strftime('%Y-%m-%d %H:%M:%S')) if not ('photos' in rsp and rsp['photos']['photo']): return for photo in rsp['photos']['photo']: yield photo page += 1
def new_album(self): with Busy(): self.save_changes() if not self.session.permitted('write'): self.refresh() return self.current_album = None self.upload_config.show_album(None) QtWidgets.QApplication.processEvents() album = PicasaNode() album.title.text = self.tr('New album') album.category.set('scheme', nsmap['gd'] + '#kind') album.category.set('term', nsmap['gphoto'] + '#album') album = self.session.new_album(album) self.upload_config.albums.addItem(album.title.text, album.id.text) self.set_current_album(album.id.text)
def copy_selected(self): if self.import_in_progress: # user has clicked while import is still cancelling self.copy_button.setChecked(False) return self.import_in_progress = True copy_list = [] for item in self.file_list_widget.selectedItems(): name = item.text().split()[0] copy_list.append(self.file_data[name]) last_item = None, datetime.min with self.session() as session: with Busy(): for item in copy_list: dest_path = item['dest_path'] dest_dir = os.path.dirname(dest_path) if self.abort_copy(): break if not os.path.isdir(dest_dir): os.makedirs(dest_dir) try: camera_file = session.copy_file(item, dest_path) if self.abort_copy(): break if camera_file: camera_file.save(dest_path) except gp.GPhoto2Error as ex: self.logger.error(str(ex)) self._fail() break timestamp = item['timestamp'] if last_item[1] < timestamp: last_item = dest_path, timestamp if self.abort_copy(): break self.image_list.open_file(dest_path) if self.abort_copy(): break QtCore.QCoreApplication.flush() if last_item[0]: self.config_store.set(self.config_section, 'last_transfer', last_item[1].isoformat(' ')) self.image_list.done_opening(last_item[0]) self.show_file_list() self.copy_button.setChecked(False) self.import_in_progress = False
def find_photos(self, min_taken_date, max_taken_date): # search Flickr page = 1 while True: with Busy(): try: rsp = self.api.people.getPhotos( user_id='me', page=page, extras='date_taken,url_t', min_taken_date=min_taken_date.strftime('%Y-%m-%d %H:%M:%S'), max_taken_date=max_taken_date.strftime('%Y-%m-%d %H:%M:%S')) if rsp['stat'] != 'ok' or not rsp['photos']['photo']: return except Exception as ex: logger.error(str(ex)) self.disconnect() return for photo in rsp['photos']['photo']: yield photo page += 1
def regenerate_thumbnail(self): # DCF spec says thumbnail must be 160 x 120, so other aspect # ratios are padded with black with Busy(): # first try using FFmpeg to make thumbnail data, fmt, w, h = self.make_thumb_ffmpeg() if not data: # use PIL or Qt qt_im = self.get_qt_image() if not qt_im: return if PIL: data, fmt, w, h = self.make_thumb_PIL(qt_im) else: data, fmt, w, h = self.make_thumb_Qt(qt_im) # set thumbnail self.metadata.thumbnail = data, fmt, w, h # reload thumbnail self.load_thumbnail()
def list_files(self): file_data = {} if self.session_factory: with self.session() as session: with Busy(): try: file_list = session.list_files() except gp.GPhoto2Error: # camera is no longer visible self._fail() return for path in file_list: try: info = session.get_file_info(path) except gp.GPhoto2Error: self._fail() return file_data[info['name']] = info self._new_file_list(file_data)