def new_set(self): dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(self.tr('Create new Flickr album')) dialog.setLayout(QtWidgets.QFormLayout()) title = SingleLineEdit(spell_check=True) dialog.layout().addRow(self.tr('Title'), title) description = MultiLineEdit(spell_check=True) dialog.layout().addRow(self.tr('Description'), description) dialog.layout().addRow(QtWidgets.QLabel( self.tr('Album will be created when photos are uploaded'))) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addRow(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return title = title.toPlainText() if not title: return description = description.toPlainText() widget = self.upload_config.add_set(title, description, index=0) widget.setChecked(True) self.photosets.insert(0, { 'id' : None, 'title' : title, 'description' : description, 'widget' : widget, })
def _replace_dialog(self, image): # has image already been uploaded? for keyword in image.metadata.keywords or []: name_pred, sep, photo_id = keyword.partition('=') if name_pred == ID_TAG: break else: # new upload return { 'set_metadata' : True, 'set_visibility': True, 'set_type' : True, 'set_albums' : True, 'replace_image' : False, 'new_photo' : True, }, None # get user preferences dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(self.tr('Replace photo')) dialog.setLayout(QtWidgets.QVBoxLayout()) message = QtWidgets.QLabel(self.tr( 'File {0} has already been uploaded to Flickr.' + ' How would you like to update it?').format( os.path.basename(image.path))) message.setWordWrap(True) dialog.layout().addWidget(message) widget = {} widget['set_metadata'] = QtWidgets.QCheckBox( self.tr('Replace metadata')) widget['set_visibility'] = QtWidgets.QCheckBox( self.tr('Change who can see it')) widget['set_type'] = QtWidgets.QCheckBox( self.tr('Change content type')) widget['set_albums'] = QtWidgets.QCheckBox( self.tr('Change album membership')) widget['replace_image'] = QtWidgets.QCheckBox( self.tr('Replace image')) widget['new_photo'] = QtWidgets.QCheckBox( self.tr('Upload as new photo')) no_upload = QtWidgets.QCheckBox(self.tr('No image upload')) no_upload.setChecked(True) button_group = QtWidgets.QButtonGroup() button_group.addButton(widget['replace_image']) button_group.addButton(widget['new_photo']) button_group.addButton(no_upload) for key in ('set_metadata', 'set_visibility', 'set_type', 'set_albums', 'replace_image', 'new_photo'): dialog.layout().addWidget(widget[key]) widget[key].setChecked(self.replace_prefs[key]) dialog.layout().addWidget(no_upload) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addWidget(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return None, photo_id for key in self.replace_prefs: self.replace_prefs[key] = widget[key].isChecked() return dict(self.replace_prefs), photo_id
def new_album(self): dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(self.tr('Create new Facebook album')) dialog.setLayout(QtWidgets.QFormLayout()) name = SingleLineEdit(spell_check=True) dialog.layout().addRow(self.tr('Title'), name) message = MultiLineEdit(spell_check=True) dialog.layout().addRow(self.tr('Description'), message) location = SingleLineEdit(spell_check=True) dialog.layout().addRow(self.tr('Location'), location) privacy = QtWidgets.QComboBox() for display_name, value in ( (self.tr('Only me'), '{value: "SELF"}'), (self.tr('All friends'), '{value: "ALL_FRIENDS"}'), (self.tr('Friends of friends'), '{value: "FRIENDS_OF_FRIENDS"}'), (self.tr('Friends + networks'), '{value: "NETWORKS_FRIENDS"}'), (self.tr('Everyone'), '{value: "EVERYONE"}'), ): privacy.addItem(display_name, value) dialog.layout().addRow(self.tr('Privacy'), privacy) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addRow(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return if not self.authorise('write'): self.refresh(force=True) return name = name.toPlainText().strip() if not name: return data = {'name': name} message = message.toPlainText().strip() if message: data['message'] = message location = location.toPlainText().strip() if location: data['location'] = location data['privacy'] = privacy.itemData(privacy.currentIndex()) try: album = self.session.post('https://graph.facebook.com/me/albums', data=data) except Exception as ex: self.logger.error(str(ex)) self.refresh(force=True) return self.load_user_data(album_id=album['id'])
def edit_template(self): dialog = QtWidgets.QDialog(parent=self) width = width_for_text(dialog, 'x' * 120) dialog.setFixedSize(min(width, self.window().width()), min(width * 3 // 4, self.window().height())) dialog.setWindowTitle(self.tr('Photini: ownership template')) dialog.setLayout(QtWidgets.QVBoxLayout()) # main dialog area form, widgets = self.data_form() widgets['copyright'].setToolTip( widgets['copyright'].toolTip() + ' ' + translate('OwnerTab', 'Use %Y to insert the year the photograph was taken.')) for key in widgets: value = self.config_store.get('ownership', key) if key == 'copyright' and not value: name = self.config_store.get('user', 'copyright_name') or '' text = (self.config_store.get('user', 'copyright_text') or translate( 'DescriptiveTab', 'Copyright ©{year} {name}.' ' All rights reserved.')) value = text.format(year='%Y', name=name) elif key == 'creator' and not value: value = self.config_store.get('user', 'creator_name') widgets[key].set_value(value) dialog.layout().addWidget(form) # apply & cancel buttons button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addWidget(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return for key in widgets: value = widgets[key].get_value() if value: self.config_store.set('ownership', key, value) else: self.config_store.delete('ownership', key)
def diff_metadata(self): dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(translate('ImageList', 'Metadata differences')) dialog.setLayout(QtWidgets.QVBoxLayout()) table = TableWidget() table.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) table.setColumnCount(3) table.setHorizontalHeaderLabels([ translate('ImageList', 'new value'), translate('ImageList', 'undo'), translate('ImageList', 'old value') ]) labels = [] row = 0 undo = {} new_md = self.metadata old_md = Metadata(self.path) for key in ('title', 'description', 'keywords', 'rating', 'copyright', 'creator', 'date_taken', 'date_digitised', 'date_modified', 'orientation', 'lens_model', 'lens_make', 'lens_serial', 'lens_spec', 'focal_length', 'focal_length_35', 'aperture', 'latlong', 'altitude', 'location_taken', 'location_shown', 'thumbnail'): values = getattr(new_md, key), getattr(old_md, key) if values[0] == values[1]: continue table.setRowCount(row + 1) for n, value in enumerate(values): if not value: value = '' elif isinstance(value, MultiString): value = '\n'.join(value) else: value = six.text_type(value) item = QtWidgets.QTableWidgetItem(value) table.setItem(row, n * 2, item) undo[key] = QtWidgets.QTableWidgetItem() undo[key].setFlags(undo[key].flags() | Qt.ItemIsUserCheckable) undo[key].setCheckState(False) table.setItem(row, 1, undo[key]) labels.append(key) row += 1 table.setVerticalHeaderLabels(labels) table.resizeColumnsToContents() table.resizeRowsToContents() dialog.layout().addWidget(table) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addWidget(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return changed = False dirty = False for key, widget in undo.items(): if widget.checkState() == Qt.Checked: setattr(new_md, key, getattr(old_md, key)) changed = True else: dirty = True if not dirty: self.reload_metadata() elif changed: self.image_list.emit_selection()
def _find_local(self, photo, unknowns): granularity = int(photo['datetakengranularity']) min_taken_date = datetime.strptime( photo['datetaken'], '%Y-%m-%d %H:%M:%S') if granularity <= 0: max_taken_date = min_taken_date + timedelta(seconds=1) elif granularity <= 4: max_taken_date = min_taken_date + timedelta(days=31) else: max_taken_date = min_taken_date + timedelta(days=366) candidates = [] for candidate in unknowns: if not candidate.metadata.date_taken: continue date_taken = candidate.metadata.date_taken['datetime'] if date_taken < min_taken_date or date_taken > max_taken_date: continue candidates.append(candidate) if not candidates: return None rsp = requests.get(photo['url_t']) if rsp.status_code == 200: flickr_icon = rsp.content else: logger.error('HTTP error %d (%s)', rsp.status_code, photo['url_t']) return None # get user to choose matching image file dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(translate('FlickrTab', 'Select an image')) dialog.setLayout(QtWidgets.QFormLayout()) dialog.layout().setFieldGrowthPolicy( QtWidgets.QFormLayout.AllNonFixedFieldsGrow) pixmap = QtGui.QPixmap() pixmap.loadFromData(flickr_icon) label = QtWidgets.QLabel() label.setPixmap(pixmap) dialog.layout().addRow(label, QtWidgets.QLabel(translate( 'FlickrTab', 'Which image file matches\nthis picture on Flickr?'))) divider = QtWidgets.QFrame() divider.setFrameStyle(QtWidgets.QFrame.HLine) dialog.layout().addRow(divider) buttons = {} for candidate in candidates: label = QtWidgets.QLabel() pixmap = candidate.image.pixmap() if pixmap: label.setPixmap(pixmap) else: label.setText(candidate.image.text()) button = QtWidgets.QPushButton( os.path.basename(candidate.path)) button.setToolTip(candidate.path) button.setCheckable(True) button.clicked.connect(dialog.accept) dialog.layout().addRow(label, button) buttons[button] = candidate button = QtWidgets.QPushButton(translate('FlickrTab', 'No match')) button.setDefault(True) button.clicked.connect(dialog.reject) dialog.layout().addRow('', button) if dialog.exec_() == QtWidgets.QDialog.Accepted: for button, candidate in buttons.items(): if button.isChecked(): return candidate return None
def do_import(self, parent): args = [ parent, self.tr('Import GPX file'), parent.app.config_store.get('paths', 'gpx', ''), self.tr("GPX files (*.gpx *.GPX *.Gpx);;All files (*)") ] if eval(parent.app.config_store.get('pyqt', 'native_dialog', 'True')): pass else: args += [None, QtWidgets.QFileDialog.DontUseNativeDialog] path = QtWidgets.QFileDialog.getOpenFileName(*args) path = path[0] if not path: return parent.app.config_store.set('paths', 'gpx', os.path.dirname(os.path.abspath(path))) # get user options config_store = QtWidgets.QApplication.instance().config_store dialog = QtWidgets.QDialog(parent=parent) dialog.setWindowTitle(self.tr('GPX options')) dialog.setLayout(QtWidgets.QFormLayout()) max_interval = QtWidgets.QSpinBox() max_interval.setRange(60, 300) max_interval.setValue( int(config_store.get('gpx_importer', 'interval', '120'))) max_interval.setSuffix(self.tr(' secs')) dialog.layout().addRow(self.tr('Max time between points'), max_interval) max_dilution = QtWidgets.QDoubleSpinBox() max_dilution.setRange(1.0, 100.0) max_dilution.setValue( float(config_store.get('gpx_importer', 'dilution', '2.5'))) max_dilution.setSingleStep(0.1) dialog.layout().addRow(self.tr('Max dilution of precision'), max_dilution) if hasattr(parent.tabs.currentWidget(), 'plot_track'): plot_track = QtWidgets.QCheckBox() plot_track.setChecked( bool(config_store.get('gpx_importer', 'plot', 'True'))) dialog.layout().addRow(self.tr('Plot track on map'), plot_track) else: plot_track = False button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok) button_box.accepted.connect(dialog.accept) dialog.layout().addWidget(button_box) dialog.exec_() max_interval = max_interval.value() max_dilution = max_dilution.value() config_store.set('gpx_importer', 'interval', max_interval) config_store.set('gpx_importer', 'dilution', max_dilution) if plot_track: plot_track = plot_track.isChecked() config_store.set('gpx_importer', 'plot', plot_track) # make a list of all points in the file points = [] discards = 0 for p in self.read_file(path): if p.horizontal_dilution and p.horizontal_dilution > max_dilution: discards += 1 continue time_stamp = p.time if time_stamp.tzinfo is not None: # convert timestamp to UTC utc_offset = time_stamp.utcoffset() time_stamp = (time_stamp - utc_offset).replace(tzinfo=None) # add point to list points.append((time_stamp, p.latitude, p.longitude)) if discards: logger.warning('Discarded %d low accuracy points', discards) if not points: logger.warning('No points found in file "%s"', path) return logger.warning('Using %d points', len(points)) # sort points by timestamp points.sort(key=lambda x: x[0]) # display on map if plot_track: # divide points into contiguous tracks tracks = [] t = [] for p in points: if t and (p[0] - t[-1][0]).total_seconds() > max_interval: tracks.append(t) t = [] t.append(p) if t: tracks.append(t) parent.tabs.currentWidget().plot_track(tracks) # set image coordinates max_interval = max_interval / 2.0 for image in parent.image_list.get_selected_images(): if not image.metadata.date_taken: continue time_stamp = image.metadata.date_taken.to_utc() if len(points) < 2: lo, hi = 0, 0 elif time_stamp < points[0][0]: lo, hi = 0, 1 elif time_stamp > points[-1][0]: lo, hi = -2, -1 else: # binary search for points with nearest timestamps lo, hi = 0, len(points) - 1 while hi - lo > 1: mid = (lo + hi) // 2 if time_stamp >= points[mid][0]: lo = mid elif time_stamp <= points[mid][0]: hi = mid # use linear interpolation (or extrapolation) to set lat & long dt_lo = (time_stamp - points[lo][0]).total_seconds() dt_hi = (time_stamp - points[hi][0]).total_seconds() if abs(dt_lo) > max_interval and abs(dt_hi) > max_interval: logger.info('No point for time %s', image.metadata.date_taken) continue if dt_lo == dt_hi: beta = 0.5 else: beta = dt_lo / (dt_lo - dt_hi) lat = points[lo][1] + (beta * (points[hi][1] - points[lo][1])) lng = points[lo][2] + (beta * (points[hi][2] - points[lo][2])) image.metadata.latlong = lat, lng parent.image_list.emit_selection()
def diff_selected_metadata(self): dialog = QtWidgets.QDialog(parent=self) dialog.setLayout(QtWidgets.QVBoxLayout()) dialog.setFixedSize(min(800, self.window().width()), min(400, self.window().height())) table = QtWidgets.QTableWidget() table.setColumnCount(3) table.setHorizontalHeaderLabels([ translate('ImageList', 'new value'), translate('ImageList', 'undo'), translate('ImageList', 'old value') ]) table.horizontalHeader().setSectionResizeMode( 0, QtWidgets.QHeaderView.Stretch) table.horizontalHeader().setSectionResizeMode( 2, QtWidgets.QHeaderView.Stretch) dialog.layout().addWidget(table) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addWidget(button_box) changed = False position = None for image in self.get_selected_images(): if not image.metadata.changed(): continue dialog.setWindowTitle( translate('ImageList', 'Metadata differences: {}').format(image.name)) labels = [] row = 0 undo = {} table.clearContents() new_md = image.metadata old_md = Metadata(image.path) for key in ('title', 'description', 'keywords', 'rating', 'copyright', 'creator', 'date_taken', 'date_digitised', 'date_modified', 'orientation', 'camera_model', 'lens_model', 'lens_spec', 'focal_length', 'focal_length_35', 'aperture', 'latlong', 'altitude', 'location_taken', 'location_shown', 'thumbnail'): values = getattr(new_md, key), getattr(old_md, key) if values[0] == values[1]: continue values = [str(x or '') for x in values] table.setRowCount(row + 1) for n, value in enumerate(values): item = QtWidgets.QTableWidgetItem(value) table.setItem(row, n * 2, item) undo[key] = QtWidgets.QTableWidgetItem() undo[key].setFlags(undo[key].flags() | Qt.ItemIsUserCheckable) undo[key].setCheckState(Qt.Unchecked) table.setItem(row, 1, undo[key]) labels.append(key) row += 1 if not row: continue table.setVerticalHeaderLabels(labels) table.resizeColumnsToContents() table.resizeRowsToContents() if position: dialog.move(position) if dialog.exec_() != QtWidgets.QDialog.Accepted: return position = dialog.pos() undo_all = True for key, widget in undo.items(): if widget.checkState() == Qt.Checked: setattr(new_md, key, getattr(old_md, key)) changed = True else: undo_all = False if undo_all: image.reload_metadata() if changed: self.emit_selection()