From: Drew Fisher Date: Wed, 26 Aug 2009 00:52:34 +0000 (-0500) Subject: Add batch photo import by timestamp and batch import preview. X-Git-Url: http://git.zarvox.org/shortlog/feed?a=commitdiff_plain;h=5127e57fb7466d4506c6599c116ef016f163f359;p=wp3.git Add batch photo import by timestamp and batch import preview. A massive improvement to the "click-and-pray" netid-only importer. This changeset provides a preview of the photos that will be imported, allowing for review before committing all the changes, as well as progress bars (since loading large photos is a time-consuming task). In addition, we now support importing large sets of photos based on relative timestamps. This will work provided you have exactly the same number of photos to import as you do people in the database without a photo. --- diff --git a/mergephotos.py b/mergephotos.py index b9c33a9..02a9c46 100644 --- a/mergephotos.py +++ b/mergephotos.py @@ -2,6 +2,38 @@ from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4.QtSql import * +class MergePreview(QDialog): + def __init__(self, parent=None): + QDialog.__init__(self, parent) + self.layout = QGridLayout() + self.okay = QPushButton("Import") + self.bad = QPushButton("Cancel") + self.scrollarea = QScrollArea() + self.scrollcontents = QWidget() + self.scrollcontents.resize(700,600) + self.innerlayout = QGridLayout() + self.scrollcontents.setLayout(self.innerlayout) + self.layout.addWidget(self.scrollarea, 0, 0, 1, 2) + self.layout.addWidget(self.okay, 1, 0 ) + self.layout.addWidget(self.bad, 1, 1 ) + self.scrollarea.setWidget(self.scrollcontents) + QObject.connect( self.okay, SIGNAL("clicked()"), self.accept ) + QObject.connect( self.bad, SIGNAL("clicked()"), self.reject ) + self.setLayout(self.layout) + self.resize(750,600) + def addToList(self, name, photo): + row = self.innerlayout.rowCount() + name_label = QLabel(name) + name_label.setAlignment(Qt.AlignCenter) + self.innerlayout.addWidget(name_label, row, 0) + pic_label = QLabel() + pic_label.setAlignment(Qt.AlignCenter) + pic_label.setPixmap(photo) + self.innerlayout.addWidget(pic_label, row, 1) + def doneAdding(self): + self.scrollcontents.resize(700, (self.innerlayout.rowCount()+1) * 300) + + class MergePhotos(QWidget): def __init__(self, parent=None, db=None): QWidget.__init__(self, parent) @@ -29,18 +61,116 @@ class MergePhotos(QWidget): dir = QDir(foldertext) #dir.setNameFilters(["*.jpg", "*.JPG"]) q = QSqlQuery(self.db) - q.exec_("SELECT id, netid FROM people WHERE photo IS NULL") + q.exec_("SELECT id, netid, forename, surname FROM people WHERE photo IS NULL") rec = q.record() col_id = rec.indexOf("id") col_netid = rec.indexOf("netid") - while q.next(): - # if netid.jpg exists, load it, write it to a QByteArray, bind it, and update that row - fileName = q.value(col_netid).toString() + QString(".jpg") + col_forename = rec.indexOf("forename") + col_surname = rec.indexOf("surname") + people = [] # Tuples of (id, netid, name) + while q.next(): # Fetch results + people.append( (q.value(col_id).toString(), q.value(col_netid).toString(), q.value(col_forename).toString() + QString(" ") + q.value(col_surname).toString() ) ) + merging_people = [] # records that have matching photos + filelist = [] # the corresponding photos + for person in people: + fileName = person[1] + QString(".jpg") if not dir.exists(fileName): - fileName = q.value(col_netid).toString() + QString(".JPG") + fileName = person[1] + QString(".JPG") if dir.exists(fileName): + merging_people.append(person) + filelist.append(fileName) + # Generate preview + preview = MergePreview(self) + progress = QProgressDialog("Preparing preview...", "Cancel", 0, len(merging_people)) + progress.setWindowModality(Qt.WindowModal) + for i in xrange(len(merging_people)): + progress.setValue(i) + if progress.wasCanceled(): + progress.setValue(len(merging_people)) + return + image = QImage() + image.load(dir.absoluteFilePath(filelist[i])) + preview.addToList( merging_people[i][2] + QString("\n") + merging_people[i][1], QPixmap.fromImage(image.scaled(QSize(400,300), Qt.KeepAspectRatio)) ) + progress.setValue(len(merging_people)) + preview.doneAdding() + + if not preview.exec_(): + return + + # If preview accepted, proceed with the import + progress = QProgressDialog("Importing photos...", "Can't cancel!", 0, len(merging_people)) + progress.setWindowModality(Qt.WindowModal) + for i in xrange(len(merging_people)): + progress.setValue(i) + progress.setLabelText(QString("Importing photo for %1...").arg(merging_people[i][2]) ) + # if netid.jpg exists, load it, write it to a QByteArray, bind it, and update that row + image = QImage() + image.load(dir.absoluteFilePath(filelist[i])) + ba = QByteArray() + buffer = QBuffer(ba) + buffer.open(QIODevice.WriteOnly) + image.save(buffer, "JPG") + buffer.close() + update_q = QSqlQuery(self.db) + update_q.prepare("UPDATE people SET photo=:photo WHERE id=:id") + update_q.bindValue(":photo", QVariant(ba)) + update_q.bindValue(":id", merging_people[i][0]) + update_q.exec_() + print "Auto-imported photo for", merging_people[i][2] + progress.setValue(len(merging_people)) + self.done() + + def beginMergeTimestamp(self): + foldertext = QFileDialog.getExistingDirectory( None, "Pick folder of photos", ".") + if not foldertext.isEmpty(): + print foldertext + dir = QDir(foldertext) + dir.setNameFilters(["*.jpg", "*.JPG"]) # Only import jpegs (for now) + dir.setFilter(QDir.Files | QDir.Readable) # Don't list subdirectories + dir.setSorting(QDir.Time) # Sort by mtime (should probably make this sort by ctime, or better, EXIF ctime, but in the general case this should work) + q = QSqlQuery(self.db) + q.exec_("SELECT id, forename, surname FROM people WHERE photo IS NULL ORDER BY createtime") + rec = q.record() + col_id = rec.indexOf("id") + col_forename = rec.indexOf("forename") + col_surname = rec.indexOf("surname") + people = [] # List of tuples (id, name) + while q.next(): + people.append( (q.value(col_id).toString(), q.value(col_forename).toString() + QString(" ") + q.value(col_surname).toString() ) ) + photofiles = dir.entryList() + print len(people), "people without photos" + print len(photofiles), "photo files provided" + if len(people) == len(photofiles): + print "merging by timestamp possible, generating preview" + preview = MergePreview(self) + # do some progress bar magic + progress = QProgressDialog("Preparing preview...", "Cancel", 0, len(people) ) + progress.setWindowModality(Qt.WindowModal) + for i in xrange(len(people)): + progress.setValue(i) + if progress.wasCanceled(): + progress.setValue(len(people)) + return image = QImage() - image.load(dir.absoluteFilePath(fileName)) + image.load(dir.absoluteFilePath(photofiles[i])) + preview.addToList(people[i][1], QPixmap.fromImage(image.scaled(QSize(400,300), Qt.KeepAspectRatio)) ) + progress.setValue(len(people)) + preview.doneAdding() + + if not preview.exec_(): + return + + # If preview accepted, proceed with the import + progress = QProgressDialog("Importing photos...", "Abort", 0, len(people)) + progress.setWindowModality(Qt.WindowModal) + for i in xrange(len(people)): + progress.setValue(i) + progress.setLabelText(QString("Importing photo for %1...").arg(people[i][1] )) + if progress.wasCanceled(): + progress.setValue(len(people)) + return + image = QImage() + image.load(dir.absoluteFilePath(photofiles[i])) ba = QByteArray() buffer = QBuffer(ba) buffer.open(QIODevice.WriteOnly) @@ -49,14 +179,13 @@ class MergePhotos(QWidget): update_q = QSqlQuery(self.db) update_q.prepare("UPDATE people SET photo=:photo WHERE id=:id") update_q.bindValue(":photo", QVariant(ba)) - update_q.bindValue(":id", q.value(col_id)) + update_q.bindValue(":id", people[i][0]) update_q.exec_() - print "Auto-imported photo for",q.value(col_netid).toString() - self.done() - - def beginMergeTimestamp(self): - print "merging by timestamp" - self.done() + print "Auto-imported photo for", people[i][1] + progress.setValue(len(people)) + self.done() + else: + print "incorrect number of photos, can't merge" def done(self): self.emit(SIGNAL("done()") )