Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions src/core/Source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,12 +252,14 @@ void Video::build()

int Video::getWidth() const
{
return this->_impl->getWidth();
int w = this->_impl->getWidth();
return w > 0 ? w : DEFAULT_WIDTH;
}

int Video::getHeight() const
{
return this->_impl->getHeight();
int h = this->_impl->getHeight();
return h > 0 ? h : DEFAULT_HEIGHT;
}

void Video::update() {
Expand Down Expand Up @@ -362,18 +364,24 @@ bool Video::setUri(const QString &uri)
// also the thread Qt Multimedia needs free to deliver those frames.
_setFallbackIcon();

const int maxAttempts = ICON_TIMEOUT / THUMBNAIL_POLL_INTERVAL;
const int firstFrameMaxAttempts = FIRST_FRAME_TIMEOUT / THUMBNAIL_POLL_INTERVAL;
const int thumbnailMaxAttempts = ICON_TIMEOUT / THUMBNAIL_POLL_INTERVAL;

if (_videoType == VIDEO_WEBCAM)
{
// No thumbnail to generate for a camera: just confirm the feed comes up.
_pollForBits([this]() {
emit frameSizeKnown(getId(), getWidth(), getHeight());
_emitPropertyChanged("icon");
}, maxAttempts);
}, firstFrameMaxAttempts);
}
else
{
_pollForBits([this, maxAttempts]() {
_pollForBits([this, thumbnailMaxAttempts]() {
// The first frame just arrived, so getWidth()/getHeight() now report
// the real resolution instead of the DEFAULT_WIDTH/HEIGHT placeholder.
emit frameSizeKnown(getId(), getWidth(), getHeight());

// Try seeking to the middle of the movie for a representative frame.
if (_impl->seekTo(0.5))
{
Expand All @@ -382,14 +390,14 @@ bool Video::setUri(const QString &uri)
qDebug() << "Could not generate thumbnail for " << _uri << ": using generic icon." << Qt::endl;
_impl->resetMovie();
_emitPropertyChanged("icon");
}, maxAttempts);
}, thumbnailMaxAttempts);
}
else
{
_impl->resetMovie();
_emitPropertyChanged("icon");
}
}, maxAttempts);
}, firstFrameMaxAttempts);
}

_emitPropertyChanged("uri");
Expand Down
22 changes: 21 additions & 1 deletion src/core/Source.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,26 @@ class Video : public Texture
Q_PROPERTY(double rate READ getRate WRITE setRate)

public:
// Thumbnail generation timeout (in ms).
// Thumbnail generation timeout (in ms): how long to wait for the *seeked*
// frame once _generateThumbnail() has asked to seek to it. Fine to give up
// quickly here — worst case is a generic icon instead of a real thumbnail.
static const int ICON_TIMEOUT = 1000;

// How long to wait for the *first* decoded frame (gates both the thumbnail
// attempt and frameSizeKnown, i.e. shape auto-fit). Much more generous than
// ICON_TIMEOUT: giving up here permanently strands any shape created before
// the deadline at the DEFAULT_WIDTH/HEIGHT placeholder, which is a real
// mis-crop, not just a missing thumbnail.
static const int FIRST_FRAME_TIMEOUT = 15000;

// How often to re-check for a decoded frame while polling (in ms).
static const int THUMBNAIL_POLL_INTERVAL = 50;

/// Frame size assumed before the first frame arrives (drives a new mapping's
/// initial input shape; auto-fitted once the real resolution is known).
static const int DEFAULT_WIDTH = 640;
static const int DEFAULT_HEIGHT = 480;

public:
Q_INVOKABLE Video(int id=NULL_UID);
Video(const QString uri_, VideoType type, double rate, uid id=NULL_UID);
Expand Down Expand Up @@ -389,6 +403,12 @@ class Video : public Texture

virtual QIcon getIcon() const { return _icon; }

signals:
/// Emitted once, the first time a real frame's resolution becomes known, so
/// the UI can fit input shapes created (at the default size) before any
/// frame had arrived.
void frameSizeKnown(int sourceId, int width, int height);

protected:

/// Starts playback.
Expand Down
64 changes: 64 additions & 0 deletions src/gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,61 @@ void MainWindow::autoFitSyphonInputShapes(int sourceId, int width, int height)
#endif
}

void MainWindow::autoFitVideoInputShapes(int sourceId, int width, int height)
{
if (width <= 0 || height <= 0)
return;

Source::ptr source = mappingManager->getSourceById(sourceId);
if (source.isNull() || source->getSourceType() != SourceType::Video)
return;

const qreal defW = Video::DEFAULT_WIDTH;
const qreal defH = Video::DEFAULT_HEIGHT;
const qreal sx = width / defW;
const qreal sy = height / defH;
const qreal eps = 1.0;

bool changed = false;
QMap<uid, Layer::ptr> layers = mappingManager->getSourceLayers(source);
for (QMap<uid, Layer::ptr>::const_iterator it = layers.constBegin();
it != layers.constEnd(); ++it)
{
Layer::ptr layer = it.value();
if (layer.isNull() || !layer->hasInputShape())
continue;

MShape::ptr input = layer->getInputShape();
QVector<QPointF> verts = input->getVertices();
if (verts.isEmpty())
continue;

// Only rescale shapes still at the untouched default size, so we never
// clobber a shape the user has already adjusted.
qreal minX = verts[0].x(), maxX = verts[0].x();
qreal minY = verts[0].y(), maxY = verts[0].y();
for (const QPointF& v : verts)
{
minX = qMin(minX, v.x()); maxX = qMax(maxX, v.x());
minY = qMin(minY, v.y()); maxY = qMax(maxY, v.y());
}
if (qAbs((maxX - minX) - defW) > eps || qAbs((maxY - minY) - defH) > eps)
continue;

for (QPointF& v : verts)
v = QPointF(v.x() * sx, v.y() * sy);
input->setVertices(verts);
input->build();
changed = true;
}

if (changed)
{
updateMappers();
updateCanvases();
}
}

void MainWindow::addMesh()
{
// A source must be selected to add a mapping.
Expand Down Expand Up @@ -3168,6 +3223,15 @@ void MainWindow::addSourceItem(uid sourceId, const QIcon& icon, const QString& n
Qt::QueuedConnection);
#endif

// Fit input shapes once a Video/Camera source's real resolution becomes
// known (it may still be the DEFAULT_WIDTH/HEIGHT placeholder if a shape
// gets added before the first frame has arrived).
if (sourceType == SourceType::Video)
connect(qSharedPointerCast<Video>(source).data(),
SIGNAL(frameSizeKnown(int, int, int)),
this, SLOT(autoFitVideoInputShapes(int, int, int)),
Qt::QueuedConnection);

// Add source item to sourceList widget.
QListWidgetItem* item = new QListWidgetItem(icon, name);
setItemId(*item, sourceId); // TODO: could possibly be replaced by a Source pointer
Expand Down
3 changes: 3 additions & 0 deletions src/gui/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ private slots:
// Fits a Syphon source's input shapes to its real resolution once known.
void autoFitSyphonInputShapes(int sourceId, int width, int height);

// Fits a Video/Camera source's input shapes to its real resolution once known.
void autoFitVideoInputShapes(int sourceId, int width, int height);

void layerPropertyChanged(uid id, QString propertyName, QVariant value);
void sourcePropertyChanged(uid id, QString propertyName, QVariant value);

Expand Down