Commit ee6f9bbc83867fb79710659035f4bff6dadc2f10

Authored by Charles Otto
1 parent 8a207670

Preliminary work on not loading complete galleries during enrollment

The basic idea is to read galleries incrementally, but there are some
complications especially related to progress counting--if we don't read a
gallery we don't know how many templates are stored in it since gallery
formats aren't nice enough to provide headers with that information.

One solution to the progress counting problem is to measure progress based
on the position of a file pointer in the gallery file (i.e. measure the
current position in the gallery file, divide by the total size of the
gallery file). This is supported by expanding the Gallery API to include a
totalSize method indicating the total size of the gallery file (or total
number of templates if that is known), and then as templates are read,
their position is stored in metadata (using the "p" key).

Several galleries are updated to respect readBlockSize, and also to store
position data in read templates.

Support for filtering out already enrolled templates in read-mode was
maintained by making the filtering an online process (part of the
enrollment pipeline) rather than a batch process done before
enrollment-proper starts.
openbr/core/core.cpp
... ... @@ -134,55 +134,51 @@ struct AlgorithmCore
134 134 if (input.name.isEmpty()) return FileList();
135 135 else gallery = getMemoryGallery(input);
136 136 }
137   - TemplateList data(TemplateList::fromGallery(input));
138 137  
139 138 bool multiProcess = Globals->file.getBool("multiProcess", false);
  139 + bool fileExclusion = false;
140 140  
141   - if (gallery.contains("append"))
142   - {
143   - // Remove any templates which are already in the gallery
144   - QScopedPointer<Gallery> g(Gallery::make(gallery));
145   - files = g->files();
146   - QSet<QString> nameSet = QSet<QString>::fromList(files.names());
147   - for (int i = data.size() - 1; i>=0; i--) {
148   - if (nameSet.contains(data[i].file.name))
149   - {
150   - data.removeAt(i);
151   - }
152   - }
  141 + // In append mode, we will exclude any templates with filenames already present in the output gallery
  142 + if (gallery.contains("append") && gallery.exists() ) {
  143 + FileList::fromGallery(gallery,true);
  144 + fileExclusion = true;
153 145 }
154 146  
155   - if (data.empty())
156   - return files;
  147 + Gallery * temp = Gallery::make(input);
  148 + qint64 total = temp->totalSize();
157 149  
158   - // Store steps for ProgressCounter
159 150 Globals->currentStep = 0;
160   - Globals->totalSteps = data.length();
  151 + Globals->totalSteps = total;
161 152  
162 153 QScopedPointer<Transform> basePipe;
163 154  
164   - if (!multiProcess)
165   - {
166   - QString pipeDesc = "GalleryOutput("+gallery.flat()+")+ProgressCounter("+QString::number(data.length())+")+Discard";
  155 + QString pipeDesc = "GalleryOutput("+gallery.flat()+")+ProgressCounter("+QString::number(total)+")+Discard";
  156 +
  157 + if (!multiProcess) {
167 158 basePipe.reset(Transform::make(pipeDesc,NULL));
168 159 CompositeTransform * downcast = dynamic_cast<CompositeTransform *>(basePipe.data());
169   - if (downcast == NULL)
170   - qFatal("downcast failed?");
171 160  
172   - // replace that placeholder with the current algorithm
  161 + if (downcast == NULL) qFatal("downcast failed?");
  162 +
173 163 downcast->transforms.prepend(this->transform.data());
  164 + if (fileExclusion) {
  165 + Transform * temp = Transform::make("FileExclusion(" + gallery.flat() + ")", downcast);
  166 + downcast->transforms.prepend(temp);
  167 + }
174 168  
175 169 // call init on the pipe to collapse the algorithm (if its top level is a pipe)
176 170 downcast->init();
177 171 }
178   - else
179   - {
180   - QString pipeDesc = "ProcessWrapper("+transformString+")"+"+GalleryOutput("+gallery.flat()+")+ProgressCounter("+QString::number(data.length())+")+Discard";
  172 + else {
  173 + pipeDesc = "ProcessWrapper("+transformString+")"+pipeDesc;
  174 + if (fileExclusion)
  175 + pipeDesc = "FileExclusion(" + gallery.flat() +")" + pipeDesc;
  176 +
181 177 basePipe.reset(Transform::make(pipeDesc,NULL));
182 178 }
183 179  
184 180 // Next, we make a Stream (with placeholder transform)
185   - QString streamDesc = "Stream(readMode=DistributeFrames)";
  181 + QString streamDesc = "Stream(readMode=StreamGallery)";
186 182 QScopedPointer<Transform> baseStream(Transform::make(streamDesc, NULL));
187 183 WrapperTransform * wrapper = dynamic_cast<WrapperTransform *> (baseStream.data());
188 184  
... ... @@ -194,9 +190,11 @@ struct AlgorithmCore
194 190  
195 191 Globals->startTime.start();
196 192  
197   - wrapper->projectUpdate(data,data);
  193 + TemplateList data, output;
  194 + data.append(input);
  195 + wrapper->projectUpdate(data, output);
198 196  
199   - files.append(data.files());
  197 + files.append(output.files());
200 198  
201 199 return files;
202 200 }
... ... @@ -337,13 +335,9 @@ struct AlgorithmCore
337 335 // comparison against the smaller gallery (which will be enrolled, and stored in memory).
338 336 bool needEnrollRows = false;
339 337  
340   -
341   -
342   -
343 338 if (output.exists() && output.get<bool>("cache", false)) return;
344 339 if (queryGallery == ".") queryGallery = targetGallery;
345 340  
346   -
347 341 // To decide which gallery is larger, we need to read both, but at this point we just want the
348 342 // metadata, and don't need the enrolled matrices.
349 343 FileList targetMetadata;
... ... @@ -359,15 +353,21 @@ struct AlgorithmCore
359 353  
360 354 File rowGallery = queryGallery;
361 355 File colGallery = targetGallery;
362   - int rowSize = queryMetadata.size();
  356 + qint64 rowSize;
363 357  
  358 + Gallery * temp;
364 359 if (transposeMode)
365 360 {
366 361 rowGallery = targetGallery;
367 362 colGallery = queryGallery;
368   - rowSize = targetMetadata.size();
  363 + temp = Gallery::make(targetGallery);
369 364 }
370   -
  365 + else
  366 + {
  367 + temp = Gallery::make(queryGallery);
  368 + }
  369 + rowSize = temp->totalSize();
  370 + delete temp;
371 371  
372 372 // Is the column gallery already enrolled? We keep the enrolled column gallery in memory, and in multi-process
373 373 // mode, every worker process retains a copy of this gallery in memory. When not in multi-process mode, we can
... ... @@ -423,8 +423,6 @@ struct AlgorithmCore
423 423 // progress counting step.
424 424 // After the base algorithm is built, the whole thing will be run in a stream, so that I/O can be handled sequentially.
425 425  
426   -
427   -
428 426 // The actual comparison step is done by a GalleryCompare transform, which has a Distance, and a gallery as data.
429 427 // Incoming templates are compared against the templates in the gallery, and the output is the resulting score
430 428 // vector.
... ...
openbr/openbr_plugin.cpp
... ... @@ -865,14 +865,14 @@ void br::Context::printStatus()
865 865 const float p = progress();
866 866 if (p < 1) {
867 867 int s = timeRemaining();
868   - fprintf(stderr, "%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g/%g \r", p*100, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(s).toStdString().c_str(), Globals->currentStep, Globals->totalSteps);
  868 + fprintf(stderr,"%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g \r", p*100, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(s).toStdString().c_str(), Globals->currentStep);
869 869 }
870 870 }
871 871  
872 872 float br::Context::progress() const
873 873 {
874 874 if (totalSteps == 0) return -1;
875   - return currentStep / totalSteps;
  875 + return currentProgress / totalSteps;
876 876 }
877 877  
878 878 void br::Context::setProperty(const QString &key, const QString &value)
... ...
openbr/openbr_plugin.h
... ... @@ -687,6 +687,10 @@ public:
687 687 Q_PROPERTY(double currentStep READ get_currentStep WRITE set_currentStep RESET reset_currentStep)
688 688 BR_PROPERTY(double, currentStep, 0)
689 689  
  690 + Q_PROPERTY(double currentProgress READ get_currentProgress WRITE set_currentProgress RESET reset_currentProgress)
  691 + BR_PROPERTY(double, currentProgress, 0)
  692 +
  693 +
690 694 /*!
691 695 * \brief Used internally to compute progress() and timeRemaining().
692 696 */
... ... @@ -1095,6 +1099,9 @@ public:
1095 1099 static Gallery *make(const File &file); /*!< \brief Make a gallery to/from a file on disk. */
1096 1100 void init();
1097 1101  
  1102 + virtual qint64 totalSize() { return std::numeric_limits<qint64>::max(); }
  1103 + virtual qint64 position() { return 0; }
  1104 +
1098 1105 private:
1099 1106 QSharedPointer<Gallery> next;
1100 1107 };
... ...
openbr/plugins/gallery.cpp
... ... @@ -23,6 +23,7 @@
23 23 #include <QSqlError>
24 24 #include <QSqlQuery>
25 25 #include <QSqlRecord>
  26 +#include <QXmlStreamReader>
26 27 #endif // BR_EMBEDDED
27 28 #include <opencv2/highgui/highgui.hpp>
28 29 #include "openbr_internal.h"
... ... @@ -127,6 +128,7 @@ class galGallery : public Gallery
127 128 Template m;
128 129 stream >> m;
129 130 templates.append(m);
  131 + templates.last().file.set("p", totalSize());
130 132 }
131 133  
132 134 *done = stream.atEnd();
... ... @@ -140,6 +142,17 @@ class galGallery : public Gallery
140 142  
141 143 stream << t;
142 144 }
  145 +
  146 + qint64 totalSize()
  147 + {
  148 + return gallery.size();
  149 + }
  150 +
  151 + qint64 position()
  152 + {
  153 + return gallery.pos();
  154 + }
  155 +
143 156 };
144 157  
145 158 BR_REGISTER(Gallery, galGallery)
... ... @@ -328,6 +341,7 @@ class memGallery : public Gallery
328 341 {
329 342 Q_OBJECT
330 343 int block;
  344 + qint64 gallerySize;
331 345  
332 346 void init()
333 347 {
... ... @@ -338,6 +352,7 @@ class memGallery : public Gallery
338 352 MemoryGalleries::galleries[file] = gallery->read();
339 353 align(MemoryGalleries::galleries[file]);
340 354 MemoryGalleries::aligned[file] = true;
  355 + gallerySize = MemoryGalleries::galleries[file].size();
341 356 }
342 357 }
343 358  
... ... @@ -349,6 +364,10 @@ class memGallery : public Gallery
349 364 }
350 365  
351 366 TemplateList templates = MemoryGalleries::galleries[file].mid(block*readBlockSize, readBlockSize);
  367 + for (qint64 i = 0; i < templates.size();i++) {
  368 + templates[i].file.set("p", i + block * readBlockSize);
  369 + }
  370 +
352 371 *done = (templates.size() < readBlockSize);
353 372 block = *done ? 0 : block+1;
354 373 return templates;
... ... @@ -389,6 +408,16 @@ class memGallery : public Gallery
389 408 templates.alignedData = alignedData;
390 409 }
391 410  
  411 + qint64 totalSize()
  412 + {
  413 + return gallerySize;
  414 + }
  415 +
  416 + qint64 position()
  417 + {
  418 + return block * readBlockSize;
  419 + }
  420 +
392 421 };
393 422  
394 423 BR_REGISTER(Gallery, memGallery)
... ... @@ -449,16 +478,19 @@ FileList FileList::fromGallery(const File &amp; file, bool cache)
449 478 *
450 479 * \see txtGallery
451 480 */
452   -class csvGallery : public Gallery
  481 +class csvGallery : public FileGallery
453 482 {
454 483 Q_OBJECT
455 484 Q_PROPERTY(int fileIndex READ get_fileIndex WRITE set_fileIndex RESET reset_fileIndex)
456 485 BR_PROPERTY(int, fileIndex, 0)
457 486  
458 487 FileList files;
  488 + QStringList headers;
459 489  
460 490 ~csvGallery()
461 491 {
  492 + f.close();
  493 +
462 494 if (files.isEmpty()) return;
463 495  
464 496 QMap<QString,QVariant> samples;
... ... @@ -496,25 +528,37 @@ class csvGallery : public Gallery
496 528  
497 529 TemplateList readBlock(bool *done)
498 530 {
499   - *done = true;
  531 + *done = false;
500 532 TemplateList templates;
501   - if (!file.exists()) return templates;
502   -
503   - QStringList lines = QtUtils::readLines(file);
  533 + if (!file.exists()) {
  534 + *done = true;
  535 + return templates;
  536 + }
504 537 QRegExp regexp("\\s*,\\s*");
505   - QStringList headers;
506   - if (!lines.isEmpty()) headers = lines.takeFirst().split(regexp);
507 538  
508   - foreach (const QString &line, lines) {
  539 + if (f.pos() == 0)
  540 + {
  541 + // read a line
  542 + QByteArray lineBytes = f.readLine();
  543 + QString line = QString::fromLocal8Bit(lineBytes).trimmed();
  544 + headers = line.split(regexp);
  545 + }
  546 +
  547 + for (qint64 i = 0; i < this->readBlockSize && !f.atEnd(); i++){
  548 + QByteArray lineBytes = f.readLine();
  549 + QString line = QString::fromLocal8Bit(lineBytes).trimmed();
  550 +
509 551 QStringList words = line.split(regexp);
510 552 if (words.size() != headers.size()) continue;
511   - File f;
512   - for (int i=0; i<words.size(); i++) {
513   - if (i == 0) f.name = words[i];
514   - else f.set(headers[i], words[i]);
  553 + File fi;
  554 + for (int j=0; j<words.size(); j++) {
  555 + if (j == 0) fi.name = words[j];
  556 + else fi.set(headers[j], words[j]);
515 557 }
516   - templates.append(f);
  558 + templates.append(fi);
  559 + templates.last().file.set("p", f.pos());
517 560 }
  561 + *done = f.atEnd();
518 562  
519 563 return templates;
520 564 }
... ... @@ -568,18 +612,12 @@ BR_REGISTER(Gallery, csvGallery)
568 612 \endverbatim
569 613 * \see csvGallery
570 614 */
571   -class txtGallery : public Gallery
  615 +class txtGallery : public FileGallery
572 616 {
573 617 Q_OBJECT
574 618 Q_PROPERTY(QString label READ get_label WRITE set_label RESET reset_label STORED false)
575 619 BR_PROPERTY(QString, label, "")
576 620  
577   - QFile f;
578   - ~txtGallery()
579   - {
580   - f.close();
581   - }
582   -
583 621 TemplateList readBlock(bool *done)
584 622 {
585 623 *done = false;
... ... @@ -597,6 +635,7 @@ class txtGallery : public Gallery
597 635 int splitIndex = line.lastIndexOf(' ');
598 636 if (splitIndex == -1) templates.append(File(line));
599 637 else templates.append(File(line.mid(0, splitIndex), line.mid(splitIndex+1)));
  638 + templates.last().file.set("p", this->position());
600 639 }
601 640  
602 641 if (f.atEnd()) {
... ... @@ -608,14 +647,6 @@ class txtGallery : public Gallery
608 647 return templates;
609 648 }
610 649  
611   - void init()
612   - {
613   - f.setFileName(file);
614   - QtUtils::touchDir(f);
615   - if (!f.open(QFile::ReadWrite))
616   - qFatal("Failed to open %s for read/write.", qPrintable(file));
617   - }
618   -
619 650 void write(const Template &t)
620 651 {
621 652 QString line = t.file.name;
... ... @@ -627,21 +658,16 @@ class txtGallery : public Gallery
627 658 };
628 659  
629 660 BR_REGISTER(Gallery, txtGallery)
  661 +
630 662 /*!
631 663 * \ingroup galleries
632 664 * \brief Treats each line as a call to File::flat()
633 665 * \author Josh Klontz \cite jklontz
634 666 */
635   -class flatGallery : public Gallery
  667 +class flatGallery : public FileGallery
636 668 {
637 669 Q_OBJECT
638 670  
639   - QFile f;
640   - ~flatGallery()
641   - {
642   - f.close();
643   - }
644   -
645 671 TemplateList readBlock(bool *done)
646 672 {
647 673 *done = false;
... ... @@ -654,8 +680,10 @@ class flatGallery : public Gallery
654 680 {
655 681 QByteArray line = f.readLine();
656 682  
657   - if (!line.isEmpty())
  683 + if (!line.isEmpty()) {
658 684 templates.append(File(QString::fromLocal8Bit(line).trimmed()));
  685 + templates.last().file.set("p", this->position());
  686 + }
659 687  
660 688 if (f.atEnd()) {
661 689 *done=true;
... ... @@ -666,15 +694,6 @@ class flatGallery : public Gallery
666 694 return templates;
667 695 }
668 696  
669   - void init()
670   - {
671   - f.setFileName(file);
672   - QtUtils::touchDir(f);
673   - if (!f.open(QFile::ReadWrite))
674   - qFatal("Failed to open %s for read/write.", qPrintable(file));
675   -
676   - }
677   -
678 697 void write(const Template &t)
679 698 {
680 699 f.write((t.file.flat()+"\n").toLocal8Bit() );
... ... @@ -688,29 +707,140 @@ BR_REGISTER(Gallery, flatGallery)
688 707 * \brief A \ref sigset input.
689 708 * \author Josh Klontz \cite jklontz
690 709 */
691   -class xmlGallery : public Gallery
  710 +class xmlGallery : public FileGallery
692 711 {
693 712 Q_OBJECT
694 713 Q_PROPERTY(bool ignoreMetadata READ get_ignoreMetadata WRITE set_ignoreMetadata RESET reset_ignoreMetadata STORED false)
695 714 BR_PROPERTY(bool, ignoreMetadata, false)
696 715 FileList files;
697 716  
  717 + QXmlStreamReader reader;
  718 +
  719 + QString currentSignatureName;
  720 + bool signatureActive;
  721 +
698 722 ~xmlGallery()
699 723 {
  724 + f.close();
700 725 if (!files.isEmpty())
701 726 BEE::writeSigset(file, files, ignoreMetadata);
702 727 }
703 728  
704 729 TemplateList readBlock(bool *done)
705 730 {
  731 + if (reader.atEnd())
  732 + f.seek(0);
  733 +
  734 + TemplateList templates;
  735 + qint64 count = 0;
  736 + while (!reader.atEnd())
  737 + {
  738 + // if an identity is active we try to read presentations
  739 + if (signatureActive)
  740 + {
  741 + while (signatureActive)
  742 + {
  743 + QXmlStreamReader::TokenType signatureToken = reader.readNext();
  744 +
  745 + // did the signature end?
  746 + if (signatureToken == QXmlStreamReader::EndElement && reader.name() == "biometric-signature") {
  747 + signatureActive = false;
  748 + break;
  749 + }
  750 + // did we reach the end of the document? Theoretically this shoudln't happen without reaching the end of
  751 + if (signatureToken == QXmlStreamReader::EndDocument)
  752 + break;
  753 +
  754 + // a presentation!
  755 + if (signatureToken == QXmlStreamReader::StartElement && reader.name() == "presentation") {
  756 + templates.append(Template(File("",currentSignatureName)));
  757 + foreach (const QXmlStreamAttribute & attribute, reader.attributes()) {
  758 + // file-name is stored directly on file, not as a key/value pair
  759 + if (attribute.name() == "file-name")
  760 + templates.last().file.name = attribute.value().toString();
  761 + // other values are directly set as metadata
  762 + else if (!ignoreMetadata) templates.last().file.set(attribute.name().toString(), attribute.value().toString());
  763 + }
  764 +
  765 + // a presentation can have bounding boxes as child elements
  766 + bool signatureActive = true;
  767 + QList<QRectF> rects = templates.last().file.rects();
  768 + while (signatureActive)
  769 + {
  770 + QXmlStreamReader::TokenType pToken = reader.readNext();
  771 + if (pToken == QXmlStreamReader::EndElement && reader.name() == "presentation")
  772 + break;
  773 +
  774 + if (pToken == QXmlStreamReader::StartElement)
  775 + {
  776 + // get boudning box properties as attributes, just going to assume this all works
  777 + qreal x = reader.attributes().value("x").toDouble();
  778 + qreal y = reader.attributes().value("y").toDouble();
  779 + qreal width = reader.attributes().value("width").toDouble();
  780 + qreal height = reader.attributes().value("height").toDouble();
  781 + rects += QRectF(x, y, width, height);
  782 + }
  783 + }
  784 + templates.last().file.setRects(rects);
  785 + templates.last().file.set("p", f.pos());
  786 +
  787 + count++;
  788 + if (count >= this->readBlockSize) {
  789 + *done = false;
  790 + return templates;
  791 + }
  792 + }
  793 + }
  794 + }
  795 + // otherwise, keep reading elements until the next identity is reacehed
  796 + else
  797 + {
  798 + QXmlStreamReader::TokenType token = reader.readNext();
  799 +
  800 + // end of file?
  801 + if (token == QXmlStreamReader::EndDocument)
  802 + break;
  803 +
  804 + // we are only interested in new elements
  805 + if (token != QXmlStreamReader::StartElement)
  806 + continue;
  807 +
  808 + QStringRef elName = reader.name();
  809 +
  810 + // biometric-signature-set is the root element
  811 + if (elName == "biometric-signature-set")
  812 + continue;
  813 +
  814 + // biometric-signature -- an identity
  815 + if (elName == "biometric-signature")
  816 + {
  817 + // read the name associated with the current signature
  818 + if (!reader.attributes().hasAttribute("name"))
  819 + {
  820 + qDebug() << "Biometric signature missing name";
  821 + continue;
  822 + }
  823 + currentSignatureName = reader.attributes().value("name").toString();
  824 + signatureActive = true;
  825 + }
  826 + }
  827 +
  828 + }
706 829 *done = true;
707   - return TemplateList(BEE::readSigset(file, ignoreMetadata));
  830 +
  831 + return templates;
708 832 }
709 833  
710 834 void write(const Template &t)
711 835 {
712 836 files.append(t.file);
713 837 }
  838 +
  839 + void init()
  840 + {
  841 + FileGallery::init();
  842 + reader.setDevice(&f);
  843 + }
714 844 };
715 845  
716 846 BR_REGISTER(Gallery, xmlGallery)
... ... @@ -1178,6 +1308,17 @@ BR_REGISTER(Gallery, vbbGallery)
1178 1308  
1179 1309 #endif
1180 1310  
  1311 +void FileGallery::init()
  1312 +{
  1313 + f.setFileName(file);
  1314 + QtUtils::touchDir(f);
  1315 + if (!f.open(QFile::ReadWrite))
  1316 + qFatal("Failed to open %s for read/write.", qPrintable(file));
  1317 + fileSize = f.size();
  1318 +
  1319 + Gallery::init();
  1320 +}
  1321 +
1181 1322 } // namespace br
1182 1323  
1183 1324 #include "gallery.moc"
... ...
openbr/plugins/misc.cpp
... ... @@ -517,14 +517,17 @@ class ProgressCounterTransform : public TimeVaryingTransform
517 517  
518 518 qint64 elapsed = timer.elapsed();
519 519  
  520 + if (!dst.empty()) {
  521 + Globals->currentProgress = dst.last().file.get<qint64>("p",0);
  522 + Globals->currentStep++;
  523 + }
  524 +
520 525 // updated every second
521 526 if (elapsed > 1000) {
522 527 Globals->printStatus();
523 528 timer.start();
524 529 }
525 530  
526   - Globals->currentStep++;
527   -
528 531 return;
529 532 }
530 533  
... ... @@ -537,12 +540,13 @@ class ProgressCounterTransform : public TimeVaryingTransform
537 540 {
538 541 (void) data;
539 542 float p = br_progress();
540   - qDebug("%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g/%g \r", p*100, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(0).toStdString().c_str(), Globals->currentStep, Globals->totalSteps);
  543 + qDebug("%05.2f%% ELAPSED=%s REMAINING=%s COUNT=%g \r", p*100, QtUtils::toTime(Globals->startTime.elapsed()/1000.0f).toStdString().c_str(), QtUtils::toTime(0).toStdString().c_str(), Globals->currentStep);
541 544 }
542 545  
543 546 void init()
544 547 {
545 548 timer.start();
  549 + Globals->currentStep = 0;
546 550 }
547 551  
548 552 public:
... ... @@ -689,6 +693,40 @@ public:
689 693  
690 694 BR_REGISTER(Transform, OutputTransform)
691 695  
  696 +class FileExclusionTransform : public UntrainableMetaTransform
  697 +{
  698 + Q_OBJECT
  699 +
  700 + Q_PROPERTY(QString exclusionGallery READ get_exclusionGallery WRITE set_exclusionGallery RESET reset_exclusionGallery STORED false)
  701 + BR_PROPERTY(QString, exclusionGallery, "")
  702 +
  703 + QSet<QString> excluded;
  704 +
  705 + void project(const Template & src, Template & dst) const
  706 + {
  707 + qFatal("FileExclusion can't do anything here");
  708 + }
  709 +
  710 + void project(const TemplateList &src, TemplateList &dst) const
  711 + {
  712 + foreach(const Template & srcTemp, src)
  713 + {
  714 + if (!excluded.contains(srcTemp.file))
  715 + dst.append(srcTemp);
  716 + }
  717 + }
  718 +
  719 + void init()
  720 + {
  721 + if (exclusionGallery.isEmpty())
  722 + return;
  723 + FileList temp = FileList::fromGallery(exclusionGallery);
  724 + excluded = QSet<QString>::fromList(temp.names());
  725 + }
  726 +};
  727 +
  728 +BR_REGISTER(Transform, FileExclusionTransform)
  729 +
692 730 }
693 731  
694 732 #include "misc.moc"
... ...
openbr/plugins/openbr_internal.h
... ... @@ -345,6 +345,21 @@ protected:
345 345 UntrainableMetadataTransform() : MetadataTransform(false) {}
346 346 };
347 347  
  348 +class FileGallery : public Gallery
  349 +{
  350 + Q_OBJECT
  351 +public:
  352 + QFile f;
  353 + qint64 fileSize;
  354 +
  355 + virtual ~FileGallery() { f.close(); }
  356 +
  357 + void init();
  358 +
  359 + qint64 totalSize() { return fileSize; }
  360 + qint64 position() { return f.pos(); }
  361 +};
  362 +
348 363 }
349 364  
350 365 #endif // OPENBR_INTERNAL_H
... ...
openbr/plugins/process.cpp
... ... @@ -497,6 +497,8 @@ class ProcessWrapperTransform : public TimeVaryingTransform
497 497  
498 498 void projectUpdate(const TemplateList &src, TemplateList &dst)
499 499 {
  500 + if (src.empty())
  501 + return;
500 502  
501 503 if (!processActive)
502 504 {
... ...