[go: up one dir, main page]

Menu

Diff of /mainwindow.cpp [000000] .. [r1]  Maximize  Restore

Switch to side-by-side view

--- a
+++ b/mainwindow.cpp
@@ -0,0 +1,860 @@
+/*
+   Copyright (C) 2011 Arnaud Champenois arthelion92@gmail.com
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include <QFileDialog>
+#include <QTime>
+#include <QErrorMessage>
+#include <QMessageBox>
+#include <QSettings>
+#include <sstream>
+#include <math.h>
+#include <QDropEvent>
+#include <QUrl>
+
+#include "videosegment.h"
+#include "kernel.hpp"
+#include "filecopier.h"
+#include "launchprocess.h"
+#include "videosegmentlistitem.h"
+#include "utils.h"
+#include "about.h"
+
+const double theMinStdDev = 5;
+
+#ifdef DEBUG
+#include <stdio.h>
+#include <stdlib.h>
+
+static FILE*theFilePt = NULL;
+void print_log(const QString&aString)
+{
+    if(theFilePt == NULL)
+     { unlink("debug.txt"); theFilePt = fopen("debug.txt","a"); }
+
+    fprintf(theFilePt,"%s\n",aString.toStdString().c_str());
+}
+#endif
+
+MainWindow::MainWindow(QWidget *parent) :
+    QMainWindow(parent),
+    ui(new Ui::MainWindow)
+{
+    ui->setupUi(this);
+    mDecoderPt = NULL;
+    mInfoLabel = new QLabel();
+    mPosLabel = new QLabel();
+    mAspectLabel = new QLabel();
+    mCorrLabel = new QLabel();
+    mSelLabel = new QLabel();
+    ui->statusBar->addWidget(mInfoLabel);
+    ui->statusBar->addWidget(mPosLabel);
+    ui->statusBar->addWidget(mAspectLabel);
+    ui->statusBar->addWidget(mCorrLabel);
+    ui->statusBar->addWidget(mSelLabel);
+    ui->VideoFrame->SetMainWindow(this);
+    mInSlider = false;
+    SetClosedState();
+
+    mInfoLabel->setText(QString("VidePub ")+QString(VIDEPUB_VERSION));
+    mMarginBefore = mMarginAfter = 10000;
+    mLastCorrelation = -1;
+    ReadSettings();
+}
+
+MainWindow::~MainWindow()
+{
+    Close();
+    SaveSettings();
+    delete ui;
+#ifdef DEBUG
+    if(theFilePt) fclose(theFilePt);
+#endif
+}
+
+void MainWindow::SetClosedState()
+{
+    ui->actionDetect_Segments->setEnabled(false);
+    ui->TimeSlider->setEnabled(false);
+    ui->PrevButton->setEnabled(false);
+    ui->NextButton->setEnabled(false);
+    ui->FastPrevButton->setEnabled(false);
+    ui->FastNextButton->setEnabled(false);
+    ui->AddBt->setEnabled(false);
+    ui->NextLogoOn->setEnabled(false);
+    ui->NextLogoOff->setEnabled(false);
+
+    SetSaveState(false);
+}
+
+void MainWindow::SetOpenedState()
+{
+    ui->actionDetect_Segments->setEnabled(false);
+    ui->TimeSlider->setEnabled(true);
+    ui->PrevButton->setEnabled(true);
+    ui->NextButton->setEnabled(true);
+    ui->FastPrevButton->setEnabled(true);
+    ui->FastNextButton->setEnabled(true);
+    ui->PrevButton->setEnabled(true);
+    ui->AddBt->setEnabled(true);
+    ui->NextLogoOn->setEnabled(false);
+    ui->NextLogoOff->setEnabled(false);
+
+    SetSaveState(false);
+}
+
+void MainWindow::SetLogoState()
+{
+    ui->actionDetect_Segments->setEnabled(true);
+    ui->TimeSlider->setEnabled(true);
+    ui->PrevButton->setEnabled(true);
+    ui->NextButton->setEnabled(true);
+    ui->FastPrevButton->setEnabled(true);
+    ui->FastNextButton->setEnabled(true);
+    ui->NextLogoOn->setEnabled(true);
+    ui->NextLogoOff->setEnabled(true);
+
+    SetSaveState(false);
+}
+
+void MainWindow::SetDetectedState()
+{
+    SetSaveState(false);
+    ui->actionDetect_Segments->setEnabled(true);
+    ui->TimeSlider->setEnabled(true);
+    ui->PrevButton->setEnabled(true);
+    ui->NextButton->setEnabled(true);
+    ui->FastPrevButton->setEnabled(true);
+    ui->FastNextButton->setEnabled(true);
+    ui->NextLogoOn->setEnabled(true);
+    ui->NextLogoOff->setEnabled(true);
+}
+
+void MainWindow::SetSaveState(bool aSelected)
+{
+    ui->GotoBeginBt->setEnabled(aSelected);
+    ui->GoToEndBt->setEnabled(aSelected);
+    ui->SetBeginBt->setEnabled(aSelected);
+    ui->SetEndBt->setEnabled(aSelected);
+    ui->DeleteBt->setEnabled(aSelected);
+    ui->MergeBt->setEnabled(aSelected);
+    ui->actionSave_selection->setEnabled(aSelected);
+}
+
+
+void MainWindow::Error(QString aMessage)
+{
+    QMessageBox::critical(this,tr("Error"),aMessage,QMessageBox::Ok,
+                          QMessageBox::NoButton);
+}
+
+
+void MainWindow::Close()
+{
+    if(mDecoderPt)
+    {
+        delete mDecoderPt; mDecoderPt = NULL;
+        mPosLabel->setText("");
+        mPosLabel->setText("");
+        ui->RegionList->clear();
+        mLogo = QImage();
+        SetClosedState();
+        mLastCorrelation = -1;
+    }
+}
+
+void MainWindow::PrintPosition()
+{
+    int64_t aPos = mDecoderPt->GetMSPosition();
+    QString aString = tr("Pos :");
+    aString +=  std::MsToString(aPos);
+    mPosLabel->setText(aString);
+    mInSlider = true;
+    ui->TimeSlider->setValue(aPos/1000);
+    int64_t aNum,aDen;
+    mDecoderPt->GetAspectRatio(aNum,aDen);
+    aString = QString("%1/%2").arg(aNum).arg(aDen);
+    mAspectLabel->setText(aString);
+
+    if(HasLogo())
+    {
+        const QImage & anImage = GetCurrentImage();
+        if(!anImage.isNull())
+        {
+            mLastCorrelation = ComputeCorrelation(anImage);
+            aString = tr("Score : %1").arg(mLastCorrelation,4,'g',2);
+            mCorrLabel->setText(aString);
+        }
+    }
+
+    mInSlider = false;
+}
+
+void MainWindow::UseFileInfo()
+{
+    QString aString = tr("Length :");
+    aString += std::MsToString(mFileInfo.mLength);
+    mInfoLabel->setText(aString);
+    aString = tr("Pos :");
+    aString += "00:00:00";
+    mPosLabel->setText(aString);
+    aString = QString("%1/%2").arg(mFileInfo.mAspectRatio[0]).arg(mFileInfo.mAspectRatio[1]);
+    mAspectLabel->setText(aString);
+    ui->TimeSlider->setMaximum(mFileInfo.mLength/1000);
+    ui->VideoFrame->resize(mFileInfo.mWidth,mFileInfo.mHeight);
+}
+
+bool MainWindow::Open(const QString&aFileName)
+{
+    if(aFileName.isEmpty()) return false;
+
+    Close();
+    mDecoderPt = new FFMPEGDecoder();
+    if(!mDecoderPt->Open(aFileName))
+    {  Close(); return false; }
+
+    mInputFileName = aFileName;
+
+    if(mDecoderPt->GetFileInfo(mFileInfo))
+        UseFileInfo();
+
+    ShowNextFrame();
+    SetOpenedState();
+
+    return true;
+}
+
+static inline double sqr(double a) { return a*a; }
+
+void MainWindow::ComputeStats(const QImage& anImage,
+                              const QRect& anArea,
+                              double*aMeanArray,
+                              double*aStdDevArray)
+{
+    double aSum[3] = { 0,0,0 };
+    double aSqrSum[3] = { 0,0,0 };
+    double aCount = anArea.height()*anArea.width();
+
+    for(int y=anArea.top(),t=0;t<anArea.height();y++,t++)
+    {
+        const uchar*aBits = anImage.scanLine(y);
+        for(int x=anArea.left(),u=0;u<anArea.width();x++,u++)
+        {
+            for(int i=0;i<3;i++)
+            {
+                aSum[i] += aBits[3*x+i];
+                aSqrSum[i] += sqr(aBits[3*x+i]);
+            }
+        }
+    }
+
+    for(int i=0;i<3;i++)
+    {
+        aMeanArray[i] = aSum[i]/aCount;
+        aStdDevArray[i] = sqrt(aSqrSum[i]/aCount-sqr(aMeanArray[i]));
+    }
+
+}
+
+double MainWindow::ComputeCorrelation(const QImage &anImageIn)
+{ 
+    double aMax = -1;
+    QRect aRect = mLogoRect;
+    const int MaxOffset = 2; // Bagottement sauce TF1
+
+    // On cherche un peu au dessus et en dessous
+    for(int anYOffset = -MaxOffset;anYOffset<=MaxOffset;anYOffset++)
+    {
+        // Et un peu sur les bords aussi
+        for(int anXOffset = -MaxOffset;anXOffset<=MaxOffset;anXOffset++)
+        {
+            aRect.moveTo(mLogoRect.left()+anXOffset,mLogoRect.top()+anYOffset);
+
+            if(aRect.intersect(anImageIn.rect()) != aRect)
+                continue;
+
+            double anImaMean[3],anImaStd[3];
+            double aCorrSum[3] = { 0,0,0 };
+
+            ComputeStats(anImageIn,aRect,anImaMean,anImaStd);
+            double aCount = aRect.height()*aRect.width();
+
+            for(int y=aRect.top(),t=0;t<aRect.height();y++,t++)
+            {
+                const uchar*anIma = anImageIn.scanLine(y);
+                const uchar*aLogo = mLogo.scanLine(t);
+                for(int x=aRect.left(),u=0;u<aRect.width();x++,u++)
+                {
+                    for(int i=0;i<3;i++)
+                    {
+                        aCorrSum[i] += ((anIma[3*x+i]-anImaMean[i])*(aLogo[3*u+i]-mMean[i]));
+                    }
+                }
+            }
+
+            double aMin = -1;
+            for(int i=0;i<3;i++)
+            {
+                // Filtrage des plans unis
+                if(anImaStd[i]<theMinStdDev || mStdDev[i]<theMinStdDev) continue;
+                aCorrSum[i] /= (aCount*mStdDev[i]*anImaStd[i]);
+                if(aCorrSum[i]<aMin || aMin == -1)
+                    aMin = aCorrSum[i];
+            }
+            if(aMin>aMax)
+                aMax = aMin;
+        }
+    }
+    return aMax;
+}
+
+void MainWindow::SetLogo(const QRect &anArea, const QImage &anImage)
+{
+  mLogo = anImage;
+  mLogoRect = anArea;
+  ComputeStats(mLogo,mLogo.rect(),mMean,mStdDev);
+  int aCount = 0;
+  for(int i=0;i<3;i++)
+  {
+      if(mStdDev[i]<theMinStdDev) aCount++;
+  }
+
+  if(aCount == 3)
+  {
+      Error(tr("Logo too uniform"));
+      mLogo = QImage();
+      mLogoRect = QRect();
+      return;
+  }
+
+  PrintPosition();
+  SetLogoState();
+  ExtractSegments();
+}
+
+void MainWindow::ShowNextFrame()
+{
+    if(IsClosed()) return;
+    QImage anImage;
+    if(mDecoderPt->ReadNextImage(anImage,true))
+    {
+        ui->VideoFrame->SetImage(anImage);
+        PrintPosition();
+    }
+}
+
+void MainWindow::FlushAll()
+{
+    QCoreApplication::processEvents();
+    QCoreApplication::sendPostedEvents();
+    QCoreApplication::flush();
+}
+
+const QImage& MainWindow::GetCurrentImage() const
+{
+    return ui->VideoFrame->GetImage();
+}
+
+bool MainWindow::ExtractSegments()
+{
+    const double aCorrelScore = 0.5;
+
+    if(IsClosed() || !HasLogo()) return false;
+
+    ui->RegionList->clear();
+    LaunchProcess aProcessDialog(mMarginBefore,mMarginAfter,this);
+    if(aProcessDialog.exec() == QDialog::Rejected)
+        return false;
+
+    mMarginBefore = aProcessDialog.GetMarginBefore();
+    mMarginAfter = aProcessDialog.GetMarginAfter();
+
+    mInSlider = true;
+
+    mDecoderPt->Rewind();
+
+    VPosition anInc = mDecoderPt->GetVPosition(10000);
+    VPosition aMarginBefore = mDecoderPt->GetVPosition(mMarginBefore);
+    VPosition aMarginAfter = mDecoderPt->GetVPosition(mMarginAfter);
+
+    int64_t aPos2 = 0;
+
+    VideoSegment aSegment;
+    int64_t aTickLength = mFileInfo.mLength;
+    VPosition aPos;
+    aPos.mMsPos = 0;
+    bool aInCorrelation = false;
+    bool aJumpCorrelation = false;
+    VPosition   aFullMargin = aMarginBefore+aMarginAfter+anInc;
+
+    QProgressDialog aProgress(this);
+    aProgress.setCancelButtonText(tr("Cancel"));
+    aProgress.setLabelText(tr("Searching..."));
+    aProgress.setRange(ui->TimeSlider->minimum(),ui->TimeSlider->maximum());
+
+    for(;aPos.mMsPos < aTickLength;)
+    {
+        if(aProgress.wasCanceled())
+            break;
+
+        if(aPos2 >= 10)
+        {
+            aProgress.setValue(aPos.mMsPos/1000);
+            FlushAll();
+            aPos2 = 0;
+        }
+        QImage aNewImage;
+        if(!mDecoderPt->ReadNextImage(aNewImage,true)) break;
+        aPos = mDecoderPt->GetPosition();
+        aPos2++;
+
+        double aCorrelation = ComputeCorrelation(aNewImage);
+        if(aCorrelation>=aCorrelScore)
+        {
+            if(aInCorrelation && aSegment.IsValid())
+                aSegment.mEnd = aPos; // continue la séquence
+            else
+            {
+                // on raffine le début à la trame près
+                if(!aSegment.IsValid() || aPos>aSegment.mEnd+anInc)
+                    mDecoderPt->SeekFrame(aPos-anInc,FFMPEGDecoder::FRAME_SEEK_SET);
+                else
+                    mDecoderPt->SeekFrame(aSegment.mEnd,FFMPEGDecoder::FRAME_SEEK_SET);
+
+                // Si par hasard on retombait dans une (ancienne) bonne corrélation, on avance
+                while(mDecoderPt->ReadNextImage(aNewImage,true) &&
+                      ComputeCorrelation(aNewImage)>=aCorrelScore);
+
+                // Puis on cherche le "vrai" début (en espérant qu'il n'y en ait pas 36 entre les deux...)
+                while(ComputeCorrelation(aNewImage)<aCorrelScore &&
+                    mDecoderPt->ReadNextImage(aNewImage,true));
+
+                aPos = mDecoderPt->GetPosition();
+
+                if(aSegment.IsValid())
+                {
+                    if(aPos>aSegment.mEnd+aFullMargin)
+                    {
+                        if(aSegment.GetLength()>anInc.mMsPos)
+                        {
+                            aSegment.mBegin -= aMarginBefore;
+                            aSegment.mEnd += aMarginAfter;
+                            new VideoSegmentListItem(aSegment,ui->RegionList);
+                        }
+                        aSegment.Clear(); // On recommence un nouveau segment
+                    }
+                    else
+                    {
+                        aSegment.mEnd = aPos;
+                    }
+                }
+
+                if(!aSegment.IsValid())
+                {
+                    aSegment.mBegin = aPos;
+                    aSegment.mEnd = aPos+mDecoderPt->GetVPosition(1);
+                }
+            }
+            aInCorrelation = true;
+            aJumpCorrelation = false;
+        }
+        else
+        {
+            if(aInCorrelation && aSegment.IsValid() && !aJumpCorrelation)
+            {
+                if(aPos>aSegment.mEnd+anInc)
+                    mDecoderPt->SeekFrame(aSegment.mEnd-anInc,FFMPEGDecoder::FRAME_SEEK_SET);
+                else
+                    mDecoderPt->SeekFrame(aSegment.mEnd,FFMPEGDecoder::FRAME_SEEK_SET);
+
+                while(mDecoderPt->ReadNextImage(aNewImage,true) &&
+                      ComputeCorrelation(aNewImage)>=aCorrelScore);
+                aPos = mDecoderPt->GetPosition();
+                aSegment.mEnd = aPos;
+                double aScore = -1;
+                // Verif qu'on a rien dans l'intervalle qui suit
+                while(mDecoderPt->ReadNextImage(aNewImage,true) &&
+                      (aPos-aSegment.mEnd)<anInc && aScore<aCorrelScore)
+                {
+                      aScore=ComputeCorrelation(aNewImage);
+                      aPos = mDecoderPt->GetPosition();
+                }
+
+                if(aScore>=aCorrelScore)
+                {
+                    aSegment.mEnd = aPos;
+                    aInCorrelation = true;
+                    aJumpCorrelation = true;
+                }
+                else
+                    aInCorrelation = false;
+            }
+            else
+                aInCorrelation = false;
+        }
+
+        if(!mDecoderPt->SeekFrame(anInc,FFMPEGDecoder::FRAME_SEEK_CUR)) break;
+    }
+
+    if(aSegment.IsValid())
+        new VideoSegmentListItem(aSegment,ui->RegionList);
+
+
+    mInSlider = false;
+
+    if(ui->RegionList->count()>0)
+        SetDetectedState();
+
+    return true;
+}
+
+void MainWindow::Rewind()
+{
+    if(!mDecoderPt) return;
+    mDecoderPt->Rewind();
+    ShowNextFrame();
+}
+
+
+/*===============================================================
+Toutes les callbacks IHM Qt
+=================================================================*/
+void MainWindow::on_actionQuitter_triggered()
+{
+    QCoreApplication * anApptPt = QCoreApplication::instance ();
+    anApptPt->exit();
+}
+
+void MainWindow::on_actionOuvrir_triggered()
+{
+    QString aFileName  = QFileDialog::getOpenFileName(this,
+                                  tr("Open video file"),
+                                  mInputFileName,
+                                  tr("MPEG stream files (*.ts)"));
+    Open(aFileName);
+
+}
+
+void MainWindow::on_actionDetect_Segments_triggered()
+{
+    ExtractSegments();
+}
+
+void MainWindow::on_GotoBeginBt_clicked()
+{
+  VideoSegment aSegment;
+  if(!ui->RegionList->GetSelection(aSegment)) return;
+
+   mDecoderPt->SeekPosition(aSegment.mBegin.mFilePos);
+   ShowNextFrame();
+}
+
+void MainWindow::on_GoToEndBt_clicked()
+{
+    VideoSegment aSegment;
+    if(!ui->RegionList->GetSelection(aSegment)) return;
+
+    mDecoderPt->SeekPosition(aSegment.mEnd.mFilePos);
+    ShowNextFrame();
+}
+
+void MainWindow::on_actionSave_selection_triggered()
+{
+    if(!mDecoderPt) return;
+
+    QList<VideoSegment> aList = ui->RegionList->GetSelectedItems();
+    if(aList.empty()) return;
+
+    QFile aFile(mLastOutputFile);
+    FileNameString aFileNameString = mLastOutputFile.toStdString();
+
+    int i = 0;
+    while(aFile.exists())
+      aFile.setFileName(aFileNameString.GetPath(i++));
+
+    QString aFileName =	QFileDialog::getSaveFileName(this,
+                                                     tr("OuputFileName"),
+                                                     aFile.fileName(),
+                                                     tr("Video stream (*.ts)"));
+    if(aFileName.isEmpty()) return;
+
+   aFile.setFileName(aFileName);
+
+    mLastOutputFile  = aFile.fileName();
+
+
+    FileCopier aCopier;
+
+    if(!aCopier.Open(mInputFileName,mLastOutputFile))
+    {
+        Error(tr("Impossible to open output file"));
+        return;
+    }
+
+    QProgressDialog aProgress(this);
+    aProgress.setCancelButtonText(tr("Cancel"));
+
+    QList<VideoSegment>::iterator anIter;
+    for(anIter = aList.begin();
+        anIter != aList.end();
+        anIter++)
+    {
+        VideoSegment&aSegment = (*anIter);
+
+        aProgress.setLabelText(QString("%1%2").arg(tr("Saving segment ")).arg(aSegment.GetString()));
+        aProgress.setRange(aSegment.mBegin.mFilePos,aSegment.mEnd.mFilePos);
+
+        if(!aCopier.Write(aSegment,&aProgress))
+        {
+            Error(tr("Saving interrupted"));
+            break;
+        }
+    }
+}
+
+void MainWindow::on_RegionList_itemSelectionChanged()
+{
+    QList<VideoSegment> aList = ui->RegionList->GetSelectedItems();
+    int64_t aLength = 0;
+    QList<VideoSegment>::iterator anIter;
+    for(anIter = aList.begin();
+        anIter != aList.end();
+        anIter++)
+    {
+        VideoSegment&aSegment = (*anIter);
+        aLength += aSegment.GetLength();
+    }
+    if(aLength>0)
+    {
+        QString aString = QString("%1%2").arg(tr("Selection :")).arg(std::MsToString(aLength));
+        mSelLabel->setText(aString);
+        SetSaveState(true);
+    }
+    else
+    {
+        SetSaveState(false);
+        mSelLabel->setText(tr("No selection"));
+    }
+}
+
+void MainWindow::dropEvent(QDropEvent *event)
+{
+    const QMimeData *mimeData = event->mimeData();
+    QUrl anURL = mimeData->urls().front();
+
+    Close();
+    QString aString = anURL.toLocalFile();
+    Open(aString);
+}
+
+void MainWindow::dragEnterEvent(QDragEnterEvent *event)
+{
+    const QMimeData *mimeData = event->mimeData();
+
+    if(mimeData->hasUrls())
+    {
+        QUrl anURL = mimeData->urls().front();
+        QString aString = anURL.toLocalFile();
+        FileNameString aFNS = aString.toAscii().data();
+        if(aFNS.TestExtension("ts"))
+        event->acceptProposedAction();
+    }
+}
+
+void MainWindow::on_TimeSlider_valueChanged(int aValue)
+{
+    if(!mDecoderPt || mInSlider) return;
+
+    int64_t aMSValue = aValue*1000;
+    if(mDecoderPt->SeekFrame(aMSValue,FFMPEGDecoder::FRAME_SEEK_SET))
+    ShowNextFrame();
+    else Error(tr("Impossible to seek in video"));
+
+}
+
+void MainWindow::on_PrevButton_clicked()
+{
+    int64_t aDeltaMSValue = -500;
+    int64_t aPos = mDecoderPt->GetMSPosition();
+
+FFWD_AGAIN:
+    if(!mDecoderPt->SeekFrame(aDeltaMSValue,FFMPEGDecoder::FRAME_SEEK_CUR)) return;
+
+    QImage anImage;
+    if(!mDecoderPt->ReadNextImage(anImage,true)) return;
+
+    if(mDecoderPt->GetMSPosition() >= aPos && aPos>-aDeltaMSValue)
+    {
+        aDeltaMSValue -= 250;
+        goto FFWD_AGAIN;
+    }
+    else
+    {
+        ui->VideoFrame->SetImage(anImage);
+        PrintPosition();
+    }
+}
+
+void MainWindow::on_FastPrevButton_clicked()
+{
+    int64_t aDeltaMSValue = -10000;
+
+    int64_t aPos = mDecoderPt->GetMSPosition();
+
+FFWD_AGAIN:
+    if(!mDecoderPt->SeekFrame(aDeltaMSValue,FFMPEGDecoder::FRAME_SEEK_CUR)) return;
+    ShowNextFrame();
+    if(mDecoderPt->GetMSPosition() >= aPos && aPos>-aDeltaMSValue)
+    {
+        aDeltaMSValue -= 10000;
+        goto FFWD_AGAIN;
+    }
+}
+
+void MainWindow::on_FastNextButton_clicked()
+{
+    const int64_t aDeltaMSValue = 10000;
+    mDecoderPt->SeekFrame(aDeltaMSValue,FFMPEGDecoder::FRAME_SEEK_CUR);
+    ShowNextFrame();
+}
+
+void MainWindow::on_NextButton_clicked()
+{
+    ShowNextFrame();
+}
+
+void MainWindow::on_MergeBt_clicked()
+{
+    ui->RegionList->MergeSelection();
+}
+
+void MainWindow::on_DeleteBt_clicked()
+{
+    ui->RegionList->DeleteSelection();
+}
+
+void MainWindow::on_SetBeginBt_clicked()
+{
+    VPosition aPos = mDecoderPt->GetPosition();
+    ui->RegionList->SetSelectionBegin(aPos);
+}
+
+void MainWindow::on_SetEndBt_clicked()
+{
+    VPosition aPos = mDecoderPt->GetPosition();
+    ui->RegionList->SetSelectionEnd(aPos);
+}
+
+void MainWindow::on_AddBt_clicked()
+{
+    VPosition aPos = mDecoderPt->GetPosition();
+    VPosition anEnd = aPos+mDecoderPt->GetVPosition(1000);
+    ui->RegionList->AddNewRegion(aPos,anEnd);
+}
+
+void MainWindow::SaveSettings()
+{
+    QSettings aSet(QSettings::IniFormat,QSettings::UserScope,"Arthelion","VidePub");
+
+    aSet.setValue("Version",1);
+    aSet.setValue("LastInputFile",mInputFileName);
+    aSet.setValue("LastOutputFile",mLastOutputFile);
+    aSet.setValue("MarginBefore",mMarginBefore);
+    aSet.setValue("MarginAfter",mMarginAfter);
+}
+
+void MainWindow::ReadSettings()
+{
+    QSettings aSet(QSettings::IniFormat,QSettings::UserScope,"Arthelion","VidePub");
+    QFile aFile(aSet.fileName());
+    if(!aFile.exists()) return;
+
+    mInputFileName = aSet.value("LastInputFile").toString();
+    mLastOutputFile = aSet.value("LastOutputFile").toString();
+    mMarginBefore = aSet.value("MarginBefore").toInt();
+    mMarginAfter = aSet.value("MarginAfter").toInt();
+}
+
+void MainWindow::on_NextLogoOn_clicked()
+{
+    if(!mDecoderPt || !HasLogo()) return;
+    const double aCorrelScore = 0.4;
+
+    QProgressDialog aProgress(this);
+    aProgress.setCancelButtonText(tr("Cancel"));
+    aProgress.setLabelText(tr("Searching..."));
+    aProgress.setRange(ui->TimeSlider->value(),ui->TimeSlider->maximum());
+    FlushAll();
+
+    QImage anImage;
+    int i=150;
+
+    while(mDecoderPt->ReadNextImage(anImage,true) &&
+          ComputeCorrelation(anImage)<aCorrelScore)
+    {
+        i++;
+        aProgress.setValue(mDecoderPt->GetMSPosition()*1000);
+        if(i==300)
+        {
+            FlushAll();
+            if(aProgress.wasCanceled()) break;
+            i=0;
+        }
+    }
+
+
+    ui->VideoFrame->SetImage(anImage);
+    PrintPosition();
+}
+
+void MainWindow::on_NextLogoOff_clicked()
+{
+    if(!mDecoderPt || !HasLogo()) return;
+    const double aCorrelScore = 0.4;
+
+    QProgressDialog aProgress(this);
+    aProgress.setCancelButtonText(tr("Cancel"));
+    aProgress.setLabelText(tr("Searching..."));
+    aProgress.setRange(ui->TimeSlider->value(),ui->TimeSlider->maximum());
+    FlushAll();
+
+    QImage anImage;
+    int i=150;
+
+    while(mDecoderPt->ReadNextImage(anImage,true) &&
+          ComputeCorrelation(anImage)>=aCorrelScore)
+    {
+        i++;
+        aProgress.setValue(mDecoderPt->GetMSPosition()*1000);
+        if(i==300)
+        {
+            FlushAll();
+            if(aProgress.wasCanceled()) break;
+            i=0;
+        }
+    }
+
+
+    ui->VideoFrame->SetImage(anImage);
+    PrintPosition();
+}
+
+void MainWindow::on_actionAbout_VidePub_triggered()
+{
+    About anAbout(this);
+    anAbout.exec();
+}