Skip to content
Snippets Groups Projects
Select Git revision
  • 78d2bc7be4306efa341044f0a746e13ca0f74e76
  • main default protected
  • automation/update protected
  • v1.3.8
  • v1.3.7
  • v1.3.6
  • v1.3.5
  • v1.3.4
  • v1.3.3
  • v1.3.2
  • v1.3.1
  • v1.3.0
  • v1.2.9
  • v1.2.8
  • v1.2.7
  • v1.2.5
  • v1.2.4
  • v1.2.3
  • v1.2.2
  • v1.2.0
  • v1.1.0
  • v1.0.0
22 results

app.py

Blame
  • player.cpp 18.94 KiB
    /*
     * PeTrack - Software for tracking pedestrians movement in videos
     * Copyright (C) 2010-2020 Forschungszentrum Jülich GmbH,
     * Maik Boltes, Juliane Adrian, Ricardo Martin Brualla, Arne Graf, Paul Häger, Daniel Hillebrand,
     * Deniz Kilic, Paul Lieberenz, Daniel Salden, Tobias Schrödter, Ann Katrin Seemann
     *
     * 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 <QApplication>
    #include <QToolButton>
    #include <QVBoxLayout>
    #include <QPixmap>
    #include <QSlider>
    #include <QStyle>
    #include <QLineEdit>
    #include <QLabel>
    #include <QIntValidator>
    #include <QtConcurrent>
    
    #include "player.h"
    #include "animation.h"
    #include "petrack.h"
    #include "control.h"
    #include "pMessageBox.h"
    
    
    Player::Player(Animation *anim, QWidget *parent) : QWidget(parent)
    {
        int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize);
        QSize iconSize(size, size);
    
        //play forward Button
        mPlayForwardButton = new QToolButton;
        mPlayForwardButton->setIcon(QPixmap(":/playF"));
        mPlayForwardButton->setIconSize(iconSize);
        connect(mPlayForwardButton, &QToolButton::clicked,this, [&](){this->play(PlayerState::FORWARD);});
    
        //play backward Button
        mPlayBackwardButton = new QToolButton;
        mPlayBackwardButton->setIcon(QPixmap(":/playB"));
        mPlayBackwardButton->setIconSize(iconSize);
        connect(mPlayBackwardButton, &QToolButton::clicked, this, [&](){this->play(PlayerState::BACKWARD);});
    
        //frame forward Button
        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()));
    
        //frame backward Button
        mFrameBackwardButton = new QToolButton;
        mFrameBackwardButton->setAutoRepeat(true);
        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()));
    
        //pause button;
        mPauseButton = new QToolButton;
        mPauseButton->setIcon(QPixmap(":/pause"));
        mPauseButton->setIconSize(iconSize);
        connect(mPauseButton,SIGNAL(clicked()),this,SLOT(pause()));
    
        //rec button;
        mRecButton = new QToolButton;
        mRecButton->setIcon(QPixmap(":/record"));
        mRecButton->setIconSize(iconSize);
        connect(mRecButton,SIGNAL(clicked()),this,SLOT(recStream()));
        mRec = false;
    
        //slider
        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 = new QLineEdit("0");
        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()));
    
        //frame number
        mFpsNum = new QLineEdit(QString::number(DEFAULT_FPS));
        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
        mPlayerSpeedLimited = false;
    
        //player layout
        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.
    {
        if (fps != -1)
        {
            mFpsNum->setText(QString::number(fps));
            mFrameForwardButton->setAutoRepeatInterval(1000./fps); // for 1000 ms / 25 fps
            mFrameBackwardButton->setAutoRepeatInterval(1000./fps); // for 1000 ms / 25 fps
        }
        mAnimation->setFPS(mFpsNum->text().toDouble());
    
    }
    void Player::setPlayerSpeedLimited(bool fixed)
    {
        mPlayerSpeedLimited = fixed;
    }
    
    void Player::togglePlayerSpeedLimited()
    {
        setPlayerSpeedLimited(!mPlayerSpeedLimited);
    }
    bool Player::getPlayerSpeedLimited() const
    {
        return mPlayerSpeedLimited;
    }
    
    void Player::setPlayerSpeedFixed(bool fixed)
    {
        mPlayerSpeedFixed = fixed;
    }
    
    void Player::setLooping(bool looping)
    {
        mLooping = looping;
    }
    
    void Player::setSpeedRelativeToRealtime(double factor)
    {
        setFPS(mAnimation->getOriginalFPS()*factor);
    }
    
    void Player::setAnim(Animation *anim)
    {
        if (anim)
        {
            pause();
            mAnimation = anim;
            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()
    {
        return mState == PlayerState::PAUSE;
    }
    
    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
     */
    bool Player::updateImage()
    {
        if (mImg.empty())
        {
            pause();
            return false;
        }
        qApp->processEvents();
    #ifdef TIME_MEASUREMENT
        double time1 = 0.0, tstart;
        tstart = clock();
    #endif
    
        QFuture<void> future = QtConcurrent::run([&]() {mMainWindow->updateImage(mImg); });
        future.waitForFinished();
    
        if (mRec)
        {
            mAviFile.appendFrame((const unsigned char*) mImg.data, true);
        }
    #ifdef TIME_MEASUREMENT
        time1 += clock() - tstart;
        time1 = time1/CLOCKS_PER_SEC;
        cout << "  time(update image) = " << time1 << " sec." << endl;
    #endif
        mSlider->setValue(mAnimation->getCurrentFrameNum()); //(1000*mAnimation->getCurrentFrameNum())/mAnimation->getNumFrames());
        mFrameNum->setText(QString().number(mAnimation->getCurrentFrameNum()));
    
        return true;
    }
    
    bool Player::forward()
    {
        qApp->processEvents();
    #ifdef TIME_MEASUREMENT
        double time1 = 0.0, tstart;
        tstart = clock();
    #endif
    
        bool should_be_last_frame = mAnimation->getCurrentFrameNum() == mAnimation->getSourceOutFrameNum();
    
        mImg = mAnimation->getNextFrame();
        // check if animation is broken somewhere in the video
        if (mImg.empty())
        {
            if (!should_be_last_frame)
            {
                debout << "Warning: video unexpected finished." << std::endl;
            }
        }
    #ifdef TIME_MEASUREMENT
        time1 += clock() - tstart;
        time1 = time1/CLOCKS_PER_SEC;
        cout << "  time(load frame) = " << time1 << " sec." << endl;
    #endif
        return updateImage();
    }
    
    bool Player::backward()
    {
        qApp->processEvents();
    #ifdef TIME_MEASUREMENT
        double time1 = 0.0, tstart;
        tstart = clock();
    #endif
        mImg = mAnimation->getPreviousFrame();
    #ifdef TIME_MEASUREMENT
        time1 += clock() - tstart;
        time1 = time1/CLOCKS_PER_SEC;
        cout << "  time(load frame) = " << time1 << " sec." << endl;
    #endif
        return updateImage();
    }
    
    
    /**
     * @brief Sets the state of the video player
     *
     * @see PlayerState
     * @param state
     */
    void Player::play(PlayerState state){
        if(mState == PlayerState::PAUSE){
            mState = state;
            playVideo();
        }else{
            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.
     */
    void Player::playVideo(){
        static QElapsedTimer timer;
        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();
            }else{
                timer.invalidate();
            }
    
    
            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(!updateImage()){
                mState = PlayerState::PAUSE;
                if( mAnimation->getCurrentFrameNum() != 0 && mAnimation->getCurrentFrameNum() != mAnimation->getSourceOutFrameNum()){
                    debout << "Warning: video unexpectedly finished." << std::endl;
                }
            }else{
                if( mLooping && mMainWindow->getControlWidget()->trackOnlineCalc->checkState() == Qt::Checked)
                {
                    PWarning(this, "Error: No tracking while looping", "Looping and tracking are incompatible. Please disable one first.");
                    mState = PlayerState::PAUSE;
                    break;
                }else if(mLooping)
                {
                    if(mState == PlayerState::FORWARD && mAnimation->getCurrentFrameNum() == mAnimation->getSourceOutFrameNum())
                    {
                        currentFrame = 0;
                    }else if(mState == PlayerState::BACKWARD && mAnimation->getCurrentFrameNum() == 0)
                    {
                        currentFrame = mAnimation->getSourceOutFrameNum();
                    }
                }
            }
        }
    
        timer.invalidate();
    }
    
    bool Player::frameForward()
    {
        pause();
        return forward();
    }
    bool Player::frameBackward()
    {
        pause();
        return backward();
    }
    
    void Player::pause()
    {
        mState = PlayerState::PAUSE;
        mMainWindow->setShowFPS(0.);
    }
    
    /**
     * @brief Toggles pause/play for use via spacebar
     */
    void Player::togglePlayPause()
    {
        static PlayerState lastState;
    
        if (mState != PlayerState::PAUSE)
        {
            lastState = mState;
            pause();
        }
        else
        {
            play(lastState);
        }
    }
    
    /**
     * @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()
     */
    void Player::recStream()
    {
        if (mAnimation->isCameraLiveStream() || mAnimation->isVideo() || mAnimation->isImageSequence() || mAnimation->isStereoVideo())
        {
            // video temp path/file name
            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("");
    
                if( fileDialog.exec() )
                    dest = fileDialog.selectedFiles().at(0);
    
                if (dest == nullptr)
                    return;
    
                if (QFile::exists(dest))
                    QFile::remove(dest);
    
                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);
    
                if (!QFile(videoTmp).copy(dest))
                {
                    PCritical(this, tr("PeTrack"),
                                                tr("Error: Could not save video file!"));
                }else
                {
                    mMainWindow->statusBar()->showMessage(tr("Saved video file to %1.").arg(dest), 5000);
                    if (!QFile(videoTmp).remove())
                    debout << "Could not remove tmp-file: " << videoTmp << std::endl;
    
                    progress.setValue(2);
                }
    
            }else // open video writer
            {
                if (mAviFile.open(videoTmp.toStdString().c_str(), mImg.cols, mImg.rows, 8*mImg.channels(), mAnimation->getFPS()))
                {
                    mRec = true;
                    mRecButton->setIcon(QPixmap(":/stop-record"));
                }else
                {
                    debout << "error: could not open video output file!" << std::endl;
                }
            }
        }
    }
    
    bool Player::skipToFrame(int f) // [0..mAnimation->getNumFrames()-1]
    {
        if (f == mAnimation->getCurrentFrameNum())
        {
            return false;
        }
        pause();
        mImg = mAnimation->getFrameAtIndex(f);
        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()));
    
        return skipToFrame(mFrameNum->text().toInt());
    }
    
    /**
     * @brief Properly updates FrameInNum and FrameOutNum
     */
    void Player::update()
    {
        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()
    {
        if (mFrameInNum->text() == "")
            return -1;
        return mFrameInNum->text().toInt();
    }
    void Player::setFrameInNum(int in)
    {
        if (in == -1)
            in = 0;
    
        mFrameInNum->setText(QString::number(in));
    
        mFrameInNumValidator->setTop(getFrameOutNum()-1);
        mFrameNumValidator->setBottom(getFrameInNum());
        mFrameNumValidator->setTop(getFrameOutNum());
    }
    int Player::getFrameOutNum()
    {
        if (mFrameOutNum->text() == "")
            return -1;
    
        return mFrameOutNum->text().toInt();
    }
    void Player::setFrameOutNum(int out)
    {
        if (out == -1)
            out = mAnimation->getMaxFrames()-1;
    
        mFrameOutNum->setText(QString::number(out));
    
        mFrameInNumValidator->setTop(/*out*/getFrameOutNum()-1);
        mFrameNumValidator->setBottom(getFrameInNum());
        mFrameNumValidator->setTop(/*out*/getFrameOutNum());
    }
    
    int Player::getPos()
    {
        return mAnimation->getCurrentFrameNum();
    }
    
    #include "moc_player.cpp"