Correctly distinguish between game and real ticks while recording video,
so that paused game and speed-up demo are recorded better.
Also support recording videos from demos executed directly (without frontend),
such videos will be automatically encoded on frontend startup.
--- a/QTfrontend/hwform.cpp Fri Jul 13 16:35:42 2012 +0400
+++ b/QTfrontend/hwform.cpp Fri Jul 13 16:39:20 2012 +0400
@@ -142,7 +142,7 @@
config = new GameUIConfig(this, cfgdir->absolutePath() + "/hedgewars.ini");
- ui.pageVideos->config = config;
+ ui.pageVideos->init(config);
#ifdef __APPLE__
panel = new M3Panel;
@@ -1428,18 +1428,7 @@
}
}
- // encode videos
- QDir videosDir(cfgdir->absolutePath() + "/VideoTemp/");
- QStringList files = videosDir.entryList(QStringList("*.txtout"), QDir::Files);
- foreach (const QString & str, files)
- {
- QString prefix = str;
- prefix.chop(7); // remove ".txtout"
- videosDir.rename(prefix + ".txtout", prefix + ".txtin"); // rename this file to not open it twice
- HWRecorder* pRecorder = new HWRecorder(config, prefix);
- ui.pageVideos->addRecorder(pRecorder);
- pRecorder->EncodeVideo(record);
- }
+ ui.pageVideos->startEncoding(record);
}
void HWForm::startTraining(const QString & scriptName)
--- a/QTfrontend/ui/page/pagevideos.cpp Fri Jul 13 16:35:42 2012 +0400
+++ b/QTfrontend/ui/page/pagevideos.cpp Fri Jul 13 16:39:20 2012 +0400
@@ -301,13 +301,7 @@
connect(btnPlay, SIGNAL(clicked()), this, SLOT(playSelectedFile()));
connect(btnDelete, SIGNAL(clicked()), this, SLOT(deleteSelectedFiles()));
connect(btnOpenDir, SIGNAL(clicked()), this, SLOT(openVideosDirectory()));
-
- QString path = cfgdir->absolutePath() + "/Videos";
- QFileSystemWatcher * pWatcher = new QFileSystemWatcher(this);
- pWatcher->addPath(path);
- connect(pWatcher, SIGNAL(directoryChanged(const QString &)), this, SLOT(updateFileList(const QString &)));
- updateFileList(path);
-}
+ }
PageVideos::PageVideos(QWidget* parent) : AbstractPage(parent),
config(0)
@@ -317,6 +311,19 @@
initPage();
}
+void PageVideos::init(GameUIConfig * config)
+{
+ this->config = config;
+
+ QString path = cfgdir->absolutePath() + "/Videos";
+ QFileSystemWatcher * pWatcher = new QFileSystemWatcher(this);
+ pWatcher->addPath(path);
+ connect(pWatcher, SIGNAL(directoryChanged(const QString &)), this, SLOT(updateFileList(const QString &)));
+ updateFileList(path);
+
+ startEncoding(); // this is for videos recorded from demos which were executed directly (without frontend)
+}
+
// user changed file format, we need to update list of codecs
void PageVideos::changeAVFormat(int index)
{
@@ -844,3 +851,32 @@
}
return list;
}
+
+void PageVideos::startEncoding(const QByteArray & record)
+{
+ QDir videoTempDir(cfgdir->absolutePath() + "/VideoTemp/");
+ QStringList files = videoTempDir.entryList(QStringList("*.txtout"), QDir::Files);
+ foreach (const QString & str, files)
+ {
+ QString prefix = str;
+ prefix.chop(7); // remove ".txtout"
+ videoTempDir.rename(prefix + ".txtout", prefix + ".txtin"); // rename this file to not open it twice
+
+ HWRecorder* pRecorder = new HWRecorder(config, prefix);
+
+ if (!record.isEmpty())
+ pRecorder->EncodeVideo(record);
+ else
+ {
+ // this is for videos recorded from demos which were executed directly (without frontend)
+ QFile demofile(videoTempDir.absoluteFilePath(prefix + ".hwd"));
+ if (!demofile.open(QIODevice::ReadOnly))
+ continue;
+ QByteArray demo = demofile.readAll();
+ if (demo.isEmpty())
+ continue;
+ pRecorder->EncodeVideo(demo);
+ }
+ addRecorder(pRecorder);
+ }
+}
--- a/QTfrontend/ui/page/pagevideos.h Fri Jul 13 16:35:42 2012 +0400
+++ b/QTfrontend/ui/page/pagevideos.h Fri Jul 13 16:39:20 2012 +0400
@@ -41,7 +41,6 @@
QCheckBox *checkUseGameRes;
QCheckBox *checkRecordAudio;
- GameUIConfig * config;
QString format()
{ return comboAVFormats->itemData(comboAVFormats->currentIndex()).toString(); }
@@ -57,6 +56,8 @@
void addRecorder(HWRecorder* pRecorder);
bool tryQuit(HWForm *form);
QString getVideosInProgress(); // get multi-line string with list of videos in progress
+ void startEncoding(const QByteArray & record = QByteArray());
+ void init(GameUIConfig * config);
private:
// virtuals from AbstractPage
@@ -76,6 +77,8 @@
void clearTemp();
void clearThumbnail();
+ GameUIConfig * config;
+
// options group
QComboBox *comboAVFormats;
QComboBox *comboVideoCodecs;
--- a/hedgewars/hwengine.pas Fri Jul 13 16:35:42 2012 +0400
+++ b/hedgewars/hwengine.pas Fri Jul 13 16:39:20 2012 +0400
@@ -82,15 +82,15 @@
end;
gsConfirm, gsGame:
begin
+ DrawWorld(Lag);
DoGameTick(Lag);
ProcessVisualGears(Lag);
- DrawWorld(Lag);
end;
gsChat:
begin
+ DrawWorld(Lag);
DoGameTick(Lag);
ProcessVisualGears(Lag);
- DrawWorld(Lag);
end;
gsExit:
begin
@@ -273,27 +273,32 @@
{$IFDEF USE_VIDEO_RECORDING}
procedure RecorderMainLoop;
-var CurrTime, PrevTime: LongInt;
+var oldGameTicks, oldRealTicks, newGameTicks, newRealTicks: LongInt;
begin
if not BeginVideoRecording() then
exit;
DoTimer(0); // gsLandGen -> gsStart
DoTimer(0); // gsStart -> gsGame
- CurrTime:= LoadNextCameraPosition();
+ if not LoadNextCameraPosition(newRealTicks, newGameTicks) then
+ exit;
fastScrolling:= true;
- DoTimer(CurrTime);
+ DoGameTick(newGameTicks);
fastScrolling:= false;
- while true do
+ oldRealTicks:= 0;
+ oldGameTicks:= newGameTicks;
+
+ while LoadNextCameraPosition(newRealTicks, newGameTicks) do
begin
+ IPCCheckSock();
+ DoGameTick(newGameTicks - oldGameTicks);
+ if GameState = gsExit then
+ break;
+ ProcessVisualGears(newRealTicks - oldRealTicks);
+ DrawWorld(newRealTicks - oldRealTicks);
EncodeFrame();
- PrevTime:= CurrTime;
- CurrTime:= LoadNextCameraPosition();
- if CurrTime = -1 then
- break;
- if DoTimer(CurrTime - PrevTime) then
- break;
- IPCCheckSock();
+ oldRealTicks:= newRealTicks;
+ oldGameTicks:= newGameTicks;
end;
StopVideoRecording();
end;
--- a/hedgewars/uVideoRec.pas Fri Jul 13 16:35:42 2012 +0400
+++ b/hedgewars/uVideoRec.pas Fri Jul 13 16:39:20 2012 +0400
@@ -39,7 +39,7 @@
var flagPrerecording: boolean = false;
function BeginVideoRecording: Boolean;
-function LoadNextCameraPosition: LongInt;
+function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean;
procedure EncodeFrame;
procedure StopVideoRecording;
@@ -88,7 +88,7 @@
audioFile: File;
numPixels: LongWord;
startTime, numFrames, curTime, progress, maxProgress: LongWord;
- cameraFilePath, soundFilePath: shortstring;
+ soundFilePath: shortstring;
thumbnailSaved : Boolean;
function BeginVideoRecording: Boolean;
@@ -98,13 +98,13 @@
{$IOCHECKS OFF}
// open file with prerecorded camera positions
- cameraFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin';
- Assign(cameraFile, cameraFilePath);
+ filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin';
+ Assign(cameraFile, filename);
Reset(cameraFile);
maxProgress:= FileSize(cameraFile);
if IOResult <> 0 then
begin
- AddFileLog('Error: Could not read from ' + cameraFilePath);
+ AddFileLog('Error: Could not read from ' + filename);
exit(false);
end;
{$IOCHECKS ON}
@@ -163,7 +163,7 @@
FreeMem(RGB_Buffer, 4*numPixels);
Close(cameraFile);
AVWrapper_Close();
- DeleteFile(cameraFilePath);
+ Erase(cameraFile);
DeleteFile(soundFilePath);
SendIPC(_S'v'); // inform frontend that we finished
end;
@@ -207,17 +207,15 @@
inc(numFrames);
end;
-// returns new game ticks
-function LoadNextCameraPosition: LongInt;
+function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean;
var frame: TFrame;
begin
- LoadNextCameraPosition:= GameTicks;
// we need to skip or duplicate frames to match target framerate
while Int64(curTime)*cVideoFramerateNum <= Int64(numFrames)*cVideoFramerateDen*1000 do
begin
{$IOCHECKS OFF}
if eof(cameraFile) then
- exit(-1);
+ exit(false);
BlockRead(cameraFile, frame, 1);
{$IOCHECKS ON}
curTime:= frame.realTicks;
@@ -226,8 +224,10 @@
zoom:= frame.zoom*cScreenWidth;
ZoomValue:= zoom;
inc(progress);
- LoadNextCameraPosition:= frame.gameTicks;
+ newRealTicks:= frame.realTicks;
+ newGameTicks:= frame.gameTicks;
end;
+ LoadNextCameraPosition:= true;
end;
// Callback which records sound.
@@ -251,6 +251,38 @@
thumbnailSaved:= true;
end;
+// copy file (free pascal doesn't have copy file function)
+procedure CopyFile(src, dest: shortstring);
+var inF, outF: file;
+ buffer: array[0..1023] of byte;
+ result: LongInt;
+begin
+{$IOCHECKS OFF}
+ result:= 0; // avoid compiler hint
+
+ Assign(inF, src);
+ Reset(inF, 1);
+ if IOResult <> 0 then
+ begin
+ AddFileLog('Error: Could not read from ' + src);
+ exit;
+ end;
+
+ Assign(outF, dest);
+ Rewrite(outF, 1);
+ if IOResult <> 0 then
+ begin
+ AddFileLog('Error: Could not write to ' + dest);
+ exit;
+ end;
+
+ repeat
+ BlockRead(inF, buffer, 1024, result);
+ BlockWrite(outF, buffer, result);
+ until result < 1024;
+{$IOCHECKS ON}
+end;
+
procedure BeginPreRecording;
var format: word;
filename: shortstring;
@@ -261,6 +293,15 @@
thumbnailSaved:= false;
RecPrefix:= 'hw-' + FormatDateTime('YYYY-MM-DD_HH-mm-ss-z', Now());
+ // If this video is recorded from demo executed directly (without frontend)
+ // then we need to copy demo so that frontend will be able to find it later.
+ if recordFileName <> '' then
+ begin
+ if GameType <> gmtDemo then // this is save and game demo is not recording, abort
+ exit;
+ CopyFile(recordFileName, UserPathPrefix + '/VideoTemp/' + RecPrefix + '.hwd');
+ end;
+
Mix_QuerySpec(@frequency, @format, @channels);
AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels));
if format <> $8010 then
--- a/hedgewars/uWorld.pas Fri Jul 13 16:35:42 2012 +0400
+++ b/hedgewars/uWorld.pas Fri Jul 13 16:39:20 2012 +0400
@@ -1392,7 +1392,7 @@
end;
// Lag alert
-if isInLag and (GameType <> gmtRecord) then
+if isInLag then
DrawSprite(sprLag, 32 - (cScreenWidth shr 1), 32, (RealTicks shr 7) mod 12);
// Wind bar