Newer
Older
/*
* PeTrack - Software for tracking pedestrians movement in videos
* Copyright (C) 2023 Forschungszentrum Jülich GmbH, IAS-7
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "player.h"
#include "animation.h"
#include "control.h"
#include "petrack.h"
#include <QIntValidator>
#include <QLabel>
#include <QLineEdit>
#include <QToolButton>
#include <QVBoxLayout>
Player::Player(Animation *anim, QWidget *parent) : QWidget(parent)
{
int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize);
mPlayForwardButton = new QToolButton;
mPlayForwardButton->setIcon(QPixmap(":/playF"));
mPlayForwardButton->setIconSize(iconSize);
connect(mPlayForwardButton, &QToolButton::clicked, this, [&]() { this->play(PlayerState::FORWARD); });
mPlayBackwardButton = new QToolButton;
mPlayBackwardButton->setIcon(QPixmap(":/playB"));
mPlayBackwardButton->setIconSize(iconSize);
connect(mPlayBackwardButton, &QToolButton::clicked, this, [&]() { this->play(PlayerState::BACKWARD); });
mFrameForwardButton = new QToolButton;
mFrameForwardButton->setAutoRepeat(true);
mFrameForwardButton->setAutoRepeatDelay(400); // before repetition starts
mFrameForwardButton->setAutoRepeatInterval(1000. / DEFAULT_FPS); // war: 40 // for 1000 ms / 25 fps
mFrameForwardButton->setIcon(QPixmap(":/skipF"));
mFrameForwardButton->setIconSize(iconSize);
connect(mFrameForwardButton, SIGNAL(clicked()), this, SLOT(frameForward()));
mFrameBackwardButton = new QToolButton;
mFrameBackwardButton->setAutoRepeatDelay(400); // before repetition starts
mFrameBackwardButton->setAutoRepeatInterval(1000. / DEFAULT_FPS); // war: 40 // for 1000 ms / 25 fps
mFrameBackwardButton->setIcon(QPixmap(":/skipB"));
mFrameBackwardButton->setIconSize(iconSize);
connect(mFrameBackwardButton, SIGNAL(clicked()), this, SLOT(frameBackward()));
mPauseButton = new QToolButton;
mPauseButton->setIcon(QPixmap(":/pause"));
mPauseButton->setIconSize(iconSize);
connect(mPauseButton, SIGNAL(clicked()), this, SLOT(pause()));
mRecButton = new QToolButton;
mRecButton->setIcon(QPixmap(":/record"));
mRecButton->setIconSize(iconSize);
connect(mRecButton, SIGNAL(clicked()), this, SLOT(recStream()));
mSlider = new QSlider(Qt::Horizontal);
mSlider->setTickPosition(QSlider::TicksAbove);
mSlider->setMinimumWidth(100);
connect(mSlider, SIGNAL(valueChanged(int)), this, SLOT(skipToFrame(int)));
// frame number
QFont f("Courier", 12, QFont::Bold); // Times Helvetica, Normal
mFrameNumValidator = new QIntValidator(0, 999999, this);
mFrameInNumValidator = new QIntValidator(0, 999999, this);
mFrameOutNumValidator = new QIntValidator(0, 999999, this);
mFrameInNum = new QLineEdit("");
mFrameInNum->setMaxLength(6);
mFrameInNum->setMaximumWidth(75);
mFrameInNum->setAlignment(Qt::AlignRight);
mFrameInNum->setValidator(mFrameInNumValidator);
mFrameInNum->setFont(f);
connect(mFrameInNum, SIGNAL(editingFinished()), this, SLOT(update()));
mFrameOutNum = new QLineEdit("");
mFrameOutNum->setMaxLength(8);
mFrameOutNum->setMaximumWidth(75);
mFrameOutNum->setAlignment(Qt::AlignRight);
mFrameOutNum->setValidator(mFrameOutNumValidator);
mFrameOutNum->setFont(f);
connect(mFrameOutNum, SIGNAL(editingFinished()), this, SLOT(update()));
mFrameNum->setMaxLength(8); // bedeutet maxminal 1,1 stunden
mFrameNum->setMaximumWidth(75); // 5*sz.width() //62
mFrameNum->setAlignment(Qt::AlignRight);
mFrameNum->setValidator(mFrameNumValidator);
mFrameNum->setFont(f);
connect(mFrameNum, SIGNAL(editingFinished()), this, SLOT(skipToFrame()));
mFpsNum->setMaxLength(8); // bedeutet maxminal 999,99
mFpsNum->setMaximumWidth(62); // 5*sz.width()
mFpsNum->setAlignment(Qt::AlignRight);
mFpsNumValidator = new QDoubleValidator(0.0, 999.99, 2, this);
mFpsNum->setValidator(mFpsNumValidator);
mFpsNum->setFont(f);
connect(mFpsNum, SIGNAL(editingFinished()), this, SLOT(setFPS()));
QFont f2("Courier", 12, QFont::Normal); // Times Helvetica, Normal
mAtLabel = new QLabel("@");
mAtLabel->setFont(f2);
mSourceInLabel = new QLabel("In:");
mSourceInLabel->setFont(f2);
mSourceOutLabel = new QLabel("Out:");
mSourceOutLabel->setFont(f2);
mFpsLabel = new QLabel("fps");
mFpsLabel->setFont(f2);
// default value
mPlayerLayout = new QHBoxLayout();
mPlayerLayout->addWidget(mPlayBackwardButton);
mPlayerLayout->addWidget(mFrameBackwardButton);
mPlayerLayout->addWidget(mPauseButton);
mPlayerLayout->addWidget(mFrameForwardButton);
mPlayerLayout->addWidget(mPlayForwardButton);
mPlayerLayout->addWidget(mRecButton);
mPlayerLayout->addWidget(mSourceInLabel);
mPlayerLayout->addWidget(mFrameInNum);
mPlayerLayout->addWidget(mSourceOutLabel);
mPlayerLayout->addWidget(mFrameOutNum);
mPlayerLayout->addWidget(mSlider);
mPlayerLayout->addWidget(mFrameNum);
mPlayerLayout->addWidget(mAtLabel);
mPlayerLayout->addWidget(mFpsNum);
mPlayerLayout->addWidget(mFpsLabel);
mPlayerLayout->setMargin(0);
mMainWindow = (class Petrack *) parent;
setLayout(mPlayerLayout);
setAnim(anim);
}
void Player::setFPS(double fps) // default: double fps=-1.
{
mFrameForwardButton->setAutoRepeatInterval(1000. / fps); // for 1000 ms / 25 fps
mFrameBackwardButton->setAutoRepeatInterval(1000. / fps); // for 1000 ms / 25 fps
void Player::setPlayerSpeedLimited(bool fixed)
setPlayerSpeedLimited(!mPlayerSpeedLimited);
bool Player::getPlayerSpeedLimited() const
void Player::setPlayerSpeedFixed(bool fixed)
{
mPlayerSpeedFixed = fixed;
}
void Player::setLooping(bool looping)
{
mLooping = looping;
}
void Player::setSpeedRelativeToRealtime(double factor)
setFPS(mAnimation->getOriginalFPS() * factor);
int max = anim->getNumFrames() > 1 ? anim->getNumFrames() - 1 : 0;
setSliderMax(max);
mFrameNumValidator->setTop(max);
mFrameInNumValidator->setTop(anim->getSourceOutFrameNum());
mFrameOutNumValidator->setTop(anim->getMaxFrames());
}
}
bool Player::getPaused()
{
}
void Player::setSliderMax(int max)
{
mSlider->setMaximum(max);
}
/**
* @brief Processes and displays the image mImg (set in forward() or backward())
*
* Heavy lifting is in Petrack::updateImage(). This method itself handles
* recording and updating the value of the video-slider.
*
* @return Boolean indicating if an frame was processed and displayed
*/
{
pause();
return false;
}
qApp->processEvents();
d.kilic
committed
mMainWindow->updateImage(mImg);
mAviFile.appendFrame((const unsigned char *) mImg.data, true);
mSlider->setValue(
mAnimation->getCurrentFrameNum()); //(1000*mAnimation->getCurrentFrameNum())/mAnimation->getNumFrames());
mFrameNum->setText(QString().number(mAnimation->getCurrentFrameNum()));
return true;
}
bool Player::forward()
{
qApp->processEvents();
bool should_be_last_frame = mAnimation->getCurrentFrameNum() == mAnimation->getSourceOutFrameNum();
SPDLOG_WARN("video unexpected finished.");
return updateImage();
}
bool Player::backward()
{
qApp->processEvents();
/**
* @brief Sets the state of the video player
*
* @see PlayerState
* @param state
*/
void Player::play(PlayerState state)
{
if(mState == PlayerState::PAUSE)
{
mState = state;
}
}
/**
* @brief Quasi MainLoop: Plays video in accordance to set frame rate
*
* This method is (indirectly) initiating calls to Player::updateImage
* and thus controls processing and display of video frames. The user has
* the option to limit playback speed, which is enforced here as well.
*
* The method is left, when the video is paused and reentered, when playing
* gets started again.
*/
int currentFrame = mAnimation->getCurrentFrameNum();
long long int overtime = 0;
while(mState != PlayerState::PAUSE)
{
// slow down the player speed for extrem fast video sequences (Jiayue China or 16fps cam99 basigo grid video)
if(mPlayerSpeedLimited || mPlayerSpeedFixed)
auto supposedDiff = static_cast<long long int>(1'000 / mAnimation->getFPS());
if(timer.isValid())
{
if(mPlayerSpeedFixed && mState == PlayerState::FORWARD)
{
overtime = std::max(0LL, overtime + (timer.elapsed() - supposedDiff));
if(overtime >= supposedDiff)
{
mAnimation->skipFrame(static_cast<int>(overtime / supposedDiff));
overtime = overtime % supposedDiff;
currentFrame =
std::min(mAnimation->getCurrentFrameNum() + 1, mAnimation->getSourceOutFrameNum());
while(!timer.hasExpired(supposedDiff))
{
qApp->processEvents();
}
}
timer.start();
switch(mState)
{
case PlayerState::FORWARD:
mImg = mAnimation->getFrameAtIndex(currentFrame);
currentFrame++;
break;
case PlayerState::BACKWARD:
mImg = mAnimation->getFrameAtIndex(currentFrame);
currentFrame--;
break;
case PlayerState::PAUSE:
break;
if(mAnimation->getCurrentFrameNum() != 0 &&
mAnimation->getCurrentFrameNum() != mAnimation->getSourceOutFrameNum())
{
SPDLOG_WARN("video unexpectedly finished.");
if(mLooping && mMainWindow->getControlWidget()->isOnlineTrackingChecked())
PWarning(
this,
"Error: No tracking while looping",
"Looping and tracking are incompatible. Please disable one first.");
if(mState == PlayerState::FORWARD &&
mAnimation->getCurrentFrameNum() == mAnimation->getSourceOutFrameNum())
currentFrame = mAnimation->getSourceInFrameNum();
else if(
mState == PlayerState::BACKWARD &&
mAnimation->getCurrentFrameNum() == mAnimation->getSourceInFrameNum())
{
currentFrame = mAnimation->getSourceOutFrameNum();
}
bool Player::frameForward()
{
pause();
return forward();
}
bool Player::frameBackward()
{
pause();
return backward();
}
void Player::pause()
{
/**
* @brief Toggles pause/play for use via spacebar
*/
/**
* @brief Toggles recording and saving of recording
*
* If already recording, the method stops the recording and saves it to
* a user-given file. If recording hasn't started, this method starts it.
*
* Actual recording happens in Player::updateImage()
*/
if(mAnimation->isCameraLiveStream() || mAnimation->isVideo() || mAnimation->isImageSequence() ||
mAnimation->isStereoVideo())
QString videoTmp = QDir::tempPath() + "/petrack-video-record.avi";
if(mRec) // stop recording and save recorded stream to disk
{
mRec = false;
mRecButton->setIcon(QPixmap(":/record"));
mAviFile.close();
QString dest;
QFileDialog fileDialog(
this,
tr("Select file for saving video output"),
nullptr,
tr("Video (*.*);;AVI-File (*.avi);;All supported types (*.avi *.mp4);;All files (*.*)"));
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
fileDialog.setFileMode(QFileDialog::AnyFile);
fileDialog.setDefaultSuffix("");
QProgressDialog progress("Save Video File", nullptr, 0, 2, mMainWindow);
progress.setWindowTitle("Save Video File");
progress.setWindowModality(Qt::WindowModal);
progress.setVisible(true);
progress.setValue(0);
progress.setLabelText(QString("save video ..."));
qApp->processEvents();
progress.setValue(1);
PCritical(this, tr("PeTrack"), tr("Error: Could not save video file!"));
}
else
{
mMainWindow->statusBar()->showMessage(tr("Saved video file to %1.").arg(dest), 5000);
SPDLOG_WARN("Could not remove tmp-file: {}", videoTmp);
if(mAviFile.open(
videoTmp.toStdString().c_str(), mImg.cols, mImg.rows, 8 * mImg.channels(), mAnimation->getFPS()))
{
mRec = true;
mRecButton->setIcon(QPixmap(":/stop-record"));
SPDLOG_ERROR("could not open video output file!");
}
}
}
}
bool Player::skipToFrame(int f) // [0..mAnimation->getNumFrames()-1]
{
if(f == mAnimation->getCurrentFrameNum())
return updateImage();
}
bool Player::skipToFrame() // [0..mAnimation->getNumFrames()-1]
{
if(mFrameNum->text().toInt() < getFrameInNum())
mFrameNum->setText(QString::number(getFrameInNum()));
if(mFrameNum->text().toInt() > getFrameOutNum())
mFrameNum->setText(QString::number(getFrameOutNum()));
/**
* @brief Properly updates FrameInNum and FrameOutNum
*/
if constexpr(true || !mMainWindow->isLoading())
if(mFrameNum->text().toInt() < mFrameInNum->text().toInt())
{
mFrameNum->setText(mFrameInNum->text());
skipToFrame(mFrameNum->text().toInt());
}
if(mFrameNum->text().toInt() > mFrameOutNum->text().toInt())
{
mFrameNum->setText(mFrameOutNum->text());
skipToFrame(mFrameNum->text().toInt());
}
mSlider->setMinimum(getFrameInNum());
mSlider->setMaximum(getFrameOutNum());
mFrameInNumValidator->setTop(getFrameOutNum() - 1);
mFrameNumValidator->setBottom(getFrameInNum());
mFrameNumValidator->setTop(getFrameOutNum());
mAnimation->updateSourceInFrameNum(mFrameInNum->text().toInt());
mAnimation->updateSourceOutFrameNum(mFrameOutNum->text().toInt());
mMainWindow->updateWindowTitle();
}
}
int Player::getFrameInNum()
{
return mFrameInNum->text().toInt();
}
void Player::setFrameInNum(int in)
{
mFrameInNumValidator->setTop(getFrameOutNum() - 1);
mFrameNumValidator->setBottom(getFrameInNum());
mFrameNumValidator->setTop(getFrameOutNum());
}
int Player::getFrameOutNum()
{
return mFrameOutNum->text().toInt();
}
void Player::setFrameOutNum(int out)
{
out = mAnimation->getMaxFrames() - 1;
mFrameInNumValidator->setTop(/*out*/ getFrameOutNum() - 1);
mFrameNumValidator->setTop(/*out*/ getFrameOutNum());
}
int Player::getPos()
{
return mAnimation->getCurrentFrameNum();
}
#include "moc_player.cpp"