Here they come - thumbnails.
authorStepan777 <stepik-777@mail.ru>
Tue, 26 Jun 2012 23:29:41 +0400
changeset 7306 3cff5c769509
parent 7303 998128081b86
child 7347 88685fbb2679
Here they come - thumbnails. Also fixing some resizing issues in pagevideos - now it resizes nicer. Wait for a announcement on hedgewars.org, I hope to make it soon.
QTfrontend/ui/page/pagevideos.cpp
hedgewars/ArgParsers.inc
hedgewars/avwrapper.c
hedgewars/hwengine.pas
hedgewars/uMisc.pas
hedgewars/uVariables.pas
hedgewars/uVideoRec.pas
--- a/QTfrontend/ui/page/pagevideos.cpp	Tue Jun 26 23:23:02 2012 +0400
+++ b/QTfrontend/ui/page/pagevideos.cpp	Tue Jun 26 23:29:41 2012 +0400
@@ -35,6 +35,7 @@
 #include <QHeaderView>
 #include <QKeyEvent>
 #include <QVBoxLayout>
+#include <QHBoxLayout>
 #include <QFileSystemWatcher>
 
 #include "hwconsts.h"
@@ -44,6 +45,8 @@
 #include "gameuiconfig.h"
 #include "recorder.h"
 
+const int ThumbnailSize = 400;
+
 // columns in table with list of video files
 enum VideosColumns
 {
@@ -90,12 +93,14 @@
 {
     QGridLayout * pPageLayout = new QGridLayout();
     pPageLayout->setColumnStretch(0, 1);
-    pPageLayout->setColumnStretch(1, 1);
+    pPageLayout->setColumnStretch(1, 2);
+    pPageLayout->setRowStretch(0, 1);
+    pPageLayout->setRowStretch(1, 1);
 
     {
         IconedGroupBox* pOptionsGroup = new IconedGroupBox(this);
         pOptionsGroup->setIcon(QIcon(":/res/graphicsicon.png"));
-        pOptionsGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+        pOptionsGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
         pOptionsGroup->setTitle(QGroupBox::tr("Video recording options"));
         QGridLayout * pOptLayout = new QGridLayout(pOptionsGroup);
 
@@ -199,7 +204,7 @@
         QStringList columns;
         columns << tr("Name");
         columns << tr("Size");
-        columns << tr("...");
+        columns << "";
 
         filesTable = new QTableWidget(pTableGroup);
         filesTable->setColumnCount(vcNumColumns);
@@ -209,11 +214,10 @@
         filesTable->verticalHeader()->hide();
 
         QHeaderView * header = filesTable->horizontalHeader();
-        int length = header->length(); // FIXME
-        // header->setResizeMode(QHeaderView::ResizeToContents);
-        header->resizeSection(vcName, length/2);
-        header->resizeSection(vcSize, length/4);
-        header->resizeSection(vcProgress, length/4);
+        header->setResizeMode(vcName, QHeaderView::ResizeToContents);
+        header->setResizeMode(vcSize, QHeaderView::Fixed);
+        header->resizeSection(vcSize, 100);
+        header->setStretchLastSection(true);
 
         btnOpenDir = new QPushButton(QPushButton::tr("Open videos directory"), pTableGroup);
 
@@ -228,21 +232,38 @@
         IconedGroupBox* pDescGroup = new IconedGroupBox(this);
         pDescGroup->setIcon(QIcon(":/res/graphicsicon.png"));
         pDescGroup->setTitle(QGroupBox::tr("Description"));
-        QGridLayout* pDescLayout = new QGridLayout(pDescGroup);
+
+        QVBoxLayout* pDescLayout = new QVBoxLayout(pDescGroup);
+        QHBoxLayout* pTopDescLayout = new QHBoxLayout(0);    // picture and text
+        QHBoxLayout* pBottomDescLayout = new QHBoxLayout(0); // buttons
 
+        // label with thumbnail picture
         labelThumbnail = new QLabel(pDescGroup);
-        pDescLayout->addWidget(labelThumbnail, 0, 0);
+        labelThumbnail->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
+        labelThumbnail->setStyleSheet(
+                    "QFrame {"
+                    "border: solid;"
+                    "border-width: 3px;"
+                    "border-color: #ffcc00;"
+                    "border-radius: 4px;"
+                    "}" );
+        pTopDescLayout->addWidget(labelThumbnail);
 
         // label with file description
         labelDesc = new QLabel(pDescGroup);
         labelDesc->setAlignment(Qt::AlignLeft | Qt::AlignTop);
-        pDescLayout->addWidget(labelDesc, 0, 1);
+        pTopDescLayout->addWidget(labelDesc);
 
         // buttons: play and delete
         btnPlay = new QPushButton(QPushButton::tr("Play"), pDescGroup);
-        pDescLayout->addWidget(btnPlay, 1, 0);
+        pBottomDescLayout->addWidget(btnPlay);
         btnDelete = new QPushButton(QPushButton::tr("Delete"), pDescGroup);
-        pDescLayout->addWidget(btnDelete, 1, 1);
+        pBottomDescLayout->addWidget(btnDelete);
+
+        pDescLayout->addStretch(1);
+        pDescLayout->addLayout(pTopDescLayout, 0);
+        pDescLayout->addStretch(1);
+        pDescLayout->addLayout(pBottomDescLayout, 0);
 
         pPageLayout->addWidget(pDescGroup, 0, 0);
     }
@@ -543,10 +564,19 @@
     VideoItem * item = nameItem(row);
     QString oldName = item->name;
     QString newName = item->text();
+    if (!newName.contains('.'))
+    {
+        // user forgot an extension
+        int pt = oldName.lastIndexOf('.');
+        if (pt != -1)
+            newName += oldName.right(oldName.length() - pt);
+    }
     item->name = newName;
     if (item->ready())
     {
-        if(!cfgdir->rename("Videos/" + oldName, "Videos/" + newName))
+        if(cfgdir->rename("Videos/" + oldName, "Videos/" + newName))
+            updateDescription();
+        else
         {
             // unable to rename for some reason (maybe user entered incorrect name),
             // therefore restore old name in cell
@@ -586,6 +616,8 @@
     item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
     filesTable->setItem(row, vcProgress, item);
 
+   // filesTable->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
+
     return row;
 }
 
@@ -597,27 +629,65 @@
 void PageVideos::updateDescription()
 {
     VideoItem * item = nameItem(filesTable->currentRow());
+    if (!item)
+    {
+        labelDesc->clear();
+        labelThumbnail->clear();
+        return;
+    }
+
     QString desc = "";
-    if (item)
+    desc += item->name + "\n";
+
+    QString thumbName = "";
+
+    if (item->ready())
     {
-        QString t = item->name;
-        desc += item->name + "\n";
-        // t.replace(".mp4", ".bmp");
-        // QMessageBox::information(this, "1", cfgdir->absoluteFilePath("Screenshots/" + t));
-       //  m_pic.load(cfgdir->absoluteFilePath("Screenshots/" + t));
-       //  m_pic = m_pic.scaledToWidth(400);
-        // m_thumbnail.setPixmap(m_pic);
+        QString path = item->path();
+        desc += tr("\nSize: ") + FileSizeStr(path) + "\n";
+        if (item->desc == "")
+            item->desc = LibavIteraction::instance().getFileInfo(path);
+        desc += item->desc;
+
+        // extract thumbnail name fron description
+        int prefixBegin = desc.indexOf("prefix[");
+        int prefixEnd = desc.indexOf("]prefix");
+        if (prefixBegin != -1 && prefixEnd != -1)
+        {
+            QString prefix = desc.mid(prefixBegin + 7, prefixEnd - (prefixBegin + 7));
+            desc.remove(prefixBegin, prefixEnd + 7 - prefixBegin);
+            thumbName = prefix;
+        }
+    }
+    else
+        desc += tr("(in progress...)");
 
+    if (thumbName.isEmpty())
+    {
         if (item->ready())
+            thumbName = item->name;
+        else
+            thumbName = item->pRecorder->name;
+        // remove extension
+        int pt = thumbName.lastIndexOf('.');
+        if (pt != -1)
+            thumbName.truncate(pt);
+    }
+
+    if (!thumbName.isEmpty())
+    {
+        thumbName = cfgdir->absoluteFilePath("VideoTemp/" + thumbName);
+        if (picThumbnail.load(thumbName + ".png") || picThumbnail.load(thumbName + ".bmp"))
         {
-            QString path = item->path();
-            desc += tr("\nSize: ") + FileSizeStr(path) + "\n";
-            if (item->desc == "")
-                item->desc = LibavIteraction::instance().getFileInfo(path);
-            desc += item->desc;
+            if (picThumbnail.width() > picThumbnail.height())
+                picThumbnail = picThumbnail.scaledToWidth(ThumbnailSize);
+            else
+                picThumbnail = picThumbnail.scaledToHeight(ThumbnailSize);
+            labelThumbnail->setMaximumSize(picThumbnail.size());
+            labelThumbnail->setPixmap(picThumbnail);
         }
         else
-            desc += tr("(in progress...)");
+            labelThumbnail->clear();
     }
     labelDesc->setText(desc);
 }
--- a/hedgewars/ArgParsers.inc	Tue Jun 26 23:23:02 2012 +0400
+++ b/hedgewars/ArgParsers.inc	Tue Jun 26 23:29:41 2012 +0400
@@ -72,7 +72,7 @@
 begin
     internalStartGameWithParameters();
     GameType:= gmtRecord;
-    cRecPrefix:= ParamStr(20);
+    RecPrefix:= ParamStr(20);
     cAVFormat:= ParamStr(21);
     cVideoCodec:= ParamStr(22);
     cVideoQuality:= StrToInt(ParamStr(23));
--- a/hedgewars/avwrapper.c	Tue Jun 26 23:23:02 2012 +0400
+++ b/hedgewars/avwrapper.c	Tue Jun 26 23:29:41 2012 +0400
@@ -90,7 +90,7 @@
 #endif
     if(!g_pAStream)
     {
-        Log("Could not allocate audio stream");
+        Log("Could not allocate audio stream\n");
         return;
     }
     g_pAStream->id = 1;
@@ -121,7 +121,7 @@
     // open it
     if (avcodec_open2(g_pAudio, g_pACodec, NULL) < 0)
     {
-        Log("Could not open audio codec %s", g_pACodec->long_name);
+        Log("Could not open audio codec %s\n", g_pACodec->long_name);
         return;
     }
 
@@ -137,7 +137,7 @@
     g_pAFrame = avcodec_alloc_frame();
     if (!g_pAFrame)
     {
-        Log("Could not allocate frame");
+        Log("Could not allocate frame\n");
         return;
     }
 }
@@ -400,7 +400,7 @@
             AddAudioStream();
         }
         else
-            Log("Could not open %s", pSoundFile);
+            Log("Could not open %s\n", pSoundFile);
     }
     else
         Log("Audio codec \"%s\" was not found; audio will be ignored.\n", pACodecName);
@@ -415,7 +415,7 @@
     if (!(g_pFormat->flags & AVFMT_NOFILE))
     {
         if (avio_open(&g_pContainer->pb, g_pContainer->filename, AVIO_FLAG_WRITE) < 0)
-            FatalError("Could not open output file (%s)", pFilename);
+            FatalError("Could not open output file (%s)", g_pContainer->filename);
     }
 
     // write the stream header, if any
--- a/hedgewars/hwengine.pas	Tue Jun 26 23:23:02 2012 +0400
+++ b/hedgewars/hwengine.pas	Tue Jun 26 23:29:41 2012 +0400
@@ -111,14 +111,18 @@
         begin
         flagMakeCapture:= false;
         {$IFDEF PAS2C}
-        s:= 'hw';
+        s:= '/Screenshots/hw';
         {$ELSE}
-        s:= 'hw_' + FormatDateTime('YYYY-MM-DD_HH-mm-ss', Now()) + inttostr(GameTicks);
+        s:= '/Screenshots/hw_' + FormatDateTime('YYYY-MM-DD_HH-mm-ss', Now()) + inttostr(GameTicks);
         {$ENDIF}
 
+        // flash
         playSound(sndShutter);
-        
-        if MakeScreenshot(s) then
+        ScreenFade:= sfFromWhite;
+        ScreenFadeValue:= sfMax;
+        ScreenFadeSpeed:= 5;
+
+        if MakeScreenshot(s, 1) then
             WriteLnToConsole('Screenshot saved: ' + s)
         else
             begin
--- a/hedgewars/uMisc.pas	Tue Jun 26 23:23:02 2012 +0400
+++ b/hedgewars/uMisc.pas	Tue Jun 26 23:29:41 2012 +0400
@@ -28,7 +28,7 @@
 
 procedure movecursor(dx, dy: LongInt);
 function  doSurfaceConversion(tmpsurf: PSDL_Surface): PSDL_Surface;
-function  MakeScreenshot(filename: shortstring): boolean;
+function  MakeScreenshot(filename: shortstring; k: LongInt): boolean;
 function  GetTeamStatString(p: PTeam): shortstring;
 {$IFDEF SDL13}
 function  SDL_RectMake(x, y, width, height: LongInt): TSDL_Rect; inline;
@@ -186,19 +186,48 @@
 
 {$ENDIF} // no PNG_SCREENSHOTS
 
+{$IFDEF USE_VIDEO_RECORDING}
+// make image k times smaller (useful for saving thumbnails)
+procedure ReduceImage(img: PByte; width, height, k: LongInt);
+var i, j, i0, j0, w, h, r, g, b: LongInt;
+begin
+    w:= width  div k;
+    h:= height div k;
+
+    // rescale inplace
+    if k <> 1 then
+    begin
+        for i:= 0 to h-1 do
+            for j:= 0 to w-1 do
+            begin
+                r:= 0;
+                g:= 0;
+                b:= 0;
+                for i0:= 0 to k-1 do
+                    for j0:= 0 to k-1 do
+                    begin
+                        r+= img[4*(width*(i*k+i0) + j*k+j0)+0];
+                        g+= img[4*(width*(i*k+i0) + j*k+j0)+1];
+                        b+= img[4*(width*(i*k+i0) + j*k+j0)+2];
+                    end;
+                img[4*(w*i + j)+0]:= r div (k*k);
+                img[4*(w*i + j)+1]:= g div (k*k);
+                img[4*(w*i + j)+2]:= b div (k*k);
+                img[4*(w*i + j)+3]:= 0;
+            end;
+    end;
+end;
+{$ENDIF}
+
 // captures and saves the screen. returns true on success.
-function MakeScreenshot(filename: shortstring): Boolean;
+// saved image will be k times smaller than original (useful for saving thumbnails).
+function MakeScreenshot(filename: shortstring; k: LongInt): Boolean;
 var p: Pointer;
     size: QWord;
     image: PScreenshot;
     format: GLenum;
     ext: string[4];
 begin
-// flash
-ScreenFade:= sfFromWhite;
-ScreenFadeValue:= sfMax;
-ScreenFadeSpeed:= 5;
-
 {$IFDEF PNG_SCREENSHOTS}
 format:= GL_RGBA;
 ext:= '.png';
@@ -218,14 +247,18 @@
     exit;
 end;
 
-// read pixel from the front buffer
+// read pixels from the front buffer
 glReadPixels(0, 0, cScreenWidth, cScreenHeight, format, GL_UNSIGNED_BYTE, p);
 
+{$IFDEF USE_VIDEO_RECORDING}
+ReduceImage(p, cScreenWidth, cScreenHeight, k);
+{$ENDIF}
+
 // allocate and fill structure that will be passed to new thread
 New(image); // will be disposed in SaveScreenshot()
-image^.filename:= UserPathPrefix + '/Screenshots/' + filename + ext;
-image^.width:= cScreenWidth;
-image^.height:= cScreenHeight;
+image^.filename:= UserPathPrefix + filename + ext;
+image^.width:= cScreenWidth div k;
+image^.height:= cScreenHeight div k;
 image^.size:= size;
 image^.buffer:= p;
 
--- a/hedgewars/uVariables.pas	Tue Jun 26 23:23:02 2012 +0400
+++ b/hedgewars/uVariables.pas	Tue Jun 26 23:29:41 2012 +0400
@@ -53,7 +53,7 @@
     cStereoMode     : TStereoMode = smNone;
     cOnlyStats      : boolean = False;
 {$IFDEF USE_VIDEO_RECORDING}
-    cRecPrefix      : shortstring;
+    RecPrefix      : shortstring;
     cAVFormat       : shortstring;
     cVideoCodec     : shortstring;
     cVideoFramerateNum : LongInt = 25;
--- a/hedgewars/uVideoRec.pas	Tue Jun 26 23:23:02 2012 +0400
+++ b/hedgewars/uVideoRec.pas	Tue Jun 26 23:29:41 2012 +0400
@@ -51,7 +51,7 @@
 
 implementation
 
-uses uVariables, uUtils, GLunit, SDLh, SysUtils, uIO;
+uses uVariables, uUtils, GLunit, SDLh, SysUtils, uIO, uMisc, uTypes;
 
 {$IFDEF WIN32}
 const AVWrapperLibName = 'libavwrapper.dll';
@@ -88,6 +88,7 @@
     numPixels: LongWord;
     startTime, numFrames: LongWord;
     cameraFilePath, soundFilePath: shortstring;
+    thumbnailSaved : Boolean;
 
 function BeginVideoRecording: Boolean;
 var filename, desc: shortstring;
@@ -98,7 +99,7 @@
 
 {$IOCHECKS OFF}
     // open file with prerecorded camera positions
-    cameraFilePath:= UserPathPrefix + '/VideoTemp/' + cRecPrefix + '.txtin';
+    cameraFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin';
     Assign(cameraFile, cameraFilePath);
     Reset(cameraFile);
     if IOResult <> 0 then
@@ -117,10 +118,11 @@
         desc+= 'Map: ' + cMapName + #10;
     if Theme <> '' then
         desc+= 'Theme: ' + Theme + #10;
+    desc+= 'prefix[' + RecPrefix + ']prefix';
     desc+= #0;
 
-    filename:= UserPathPrefix + '/VideoTemp/' + cRecPrefix + #0;
-    soundFilePath:= UserPathPrefix + '/VideoTemp/' + cRecPrefix + '.sw' + #0;
+    filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + #0;
+    soundFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.sw' + #0;
     cAVFormat+= #0;
     cAudioCodec+= #0;
     cVideoCodec+= #0;
@@ -222,9 +224,25 @@
 {$IOCHECKS ON}
 end;
 
+procedure SaveThumbnail;
+var thumbpath: shortstring;
+    k: LongInt;
+begin
+    thumbpath:= '/VideoTemp/' + RecPrefix;
+    AddFileLog('Saving thumbnail ' + thumbpath);
+    if cScreenWidth > cScreenHeight then
+        k:= cScreenWidth div 400  // here 400 is minimum size of thumbnail
+    else
+        k:= cScreenHeight div 400;
+    if k = 0 then
+        k:= 1;
+    MakeScreenshot(thumbpath, k);
+    thumbnailSaved:= true;
+end;
+
 procedure BeginPreRecording;
 var format: word;
-    filePrefix, filename: shortstring;
+    filename: shortstring;
     frequency, channels: LongInt;
 begin
     AddFileLog('BeginPreRecording');
@@ -232,7 +250,11 @@
     numFrames:= 0;
     startTime:= SDL_GetTicks();
 
-    filePrefix:= FormatDateTime('YYYY-MM-DD_HH-mm-ss', Now());
+    RecPrefix:= FormatDateTime('YYYY-MM-DD_HH-mm-ss', Now());
+
+    thumbnailSaved:= false;
+    if (not (gameState in [gsLandGen, gsStart])) and (ScreenFade = sfNone) then
+        SaveThumbnail();
 
     Mix_QuerySpec(@frequency, @format, @channels);
     AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels));
@@ -245,7 +267,7 @@
 
 {$IOCHECKS OFF}
     // create sound file
-    filename:= UserPathPrefix + '/VideoTemp/' + filePrefix + '.sw';
+    filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.sw';
     Assign(audioFile, filename);
     Rewrite(audioFile, 1);
     if IOResult <> 0 then
@@ -255,7 +277,7 @@
     end;
 
     // create file with camera positions
-    filename:= UserPathPrefix + '/VideoTemp/' + filePrefix + '.txtout';
+    filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtout';
     Assign(cameraFile, filename);
     Rewrite(cameraFile);
     if IOResult <> 0 then
@@ -285,12 +307,18 @@
     Close(cameraFile);
     Mix_SetPostMix(nil, nil);
     SDL_UnlockAudio();
+
+    if (not thumbnailSaved) then
+        SaveThumbnail();
 end;
 
 procedure SaveCameraPosition;
 var curTime: LongInt;
     frame: TFrame;
 begin
+    if (not thumbnailSaved) and (ScreenFade = sfNone) then
+        SaveThumbnail();
+
     curTime:= SDL_GetTicks();
     while Int64(curTime - startTime)*cVideoFramerateNum > Int64(numFrames)*cVideoFramerateDen*1000 do
     begin