Salome HOME
1be7fe354deca8cd4f6b05875ef891775545302c
[modules/gui.git] / src / SVTK / SVTK_Recorder.cxx
1 // Copyright (C) 2007-2023  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include "SVTK_Recorder.h"
21
22 #include "SVTK_ImageWriter.h"
23 #include "SVTK_ImageWriterMgr.h"
24
25 #include <vtkObjectFactory.h>
26 #include <vtkObject.h>
27 #include <vtkCallbackCommand.h>
28 #include <vtkRenderWindow.h>
29 #include <vtkTimerLog.h>
30 #include <vtkWindowToImageFilter.h>
31 #include <vtkJPEGWriter.h>
32 #include <vtkImageData.h>
33
34 #include <sstream>
35 #include <iomanip>
36 #include <iostream>
37 #include <math.h>
38
39 #ifndef WIN32
40 #include <unistd.h>
41 #endif
42
43 #include <QApplication>
44 #include <QFileInfo>
45 #include <QDir>
46
47 #include "utilities.h"
48
49
50 namespace
51 {
52   //----------------------------------------------------------------------------
53   inline
54   void
55   GetNameJPEG(const std::string& thePreffix,
56               const int theIndex,
57               std::string& theName)
58   {
59     std::ostringstream aStream;
60     aStream<<thePreffix<<"_"<<setw(6)<<setfill('0')<<theIndex<<".jpeg";
61     theName = aStream.str();
62   }
63 }
64
65 //----------------------------------------------------------------------------
66 vtkStandardNewMacro(SVTK_Recorder)
67
68
69 //----------------------------------------------------------------------------
70 SVTK_Recorder
71 ::SVTK_Recorder():
72   myState(SVTK_Recorder_Stop),
73   myPaused(0),
74   myErrorStatus(0),
75   myPriority(0.0),
76   myTimeStart(0.0),
77   myFrameIndex(0),
78   myNbWrittenFrames(0),
79   myNbFPS(5.5),
80   myQuality(100),
81   myProgressiveMode(true),
82   myUseSkippedFrames(true),
83   myNameAVIMaker("jpeg2yuv"),
84   myCommand(vtkCallbackCommand::New()),
85   myRenderWindow(NULL),
86   myFilter(vtkWindowToImageFilter::New()),
87   myWriterMgr(new SVTK_ImageWriterMgr)
88 {
89   myCommand->SetClientData(this);
90   myCommand->SetCallback(SVTK_Recorder::ProcessEvents);
91 }
92
93
94 //----------------------------------------------------------------------------
95 SVTK_Recorder
96 ::~SVTK_Recorder()
97 {
98   myCommand->Delete();
99   myFilter->Delete();
100   delete myWriterMgr;
101 }
102
103
104 //----------------------------------------------------------------------------
105 void
106 SVTK_Recorder
107 ::CheckExistAVIMaker()
108 {
109   myErrorStatus = 0;
110   std::ostringstream aStream;
111 #ifndef WIN32
112   aStream<<"which "<<myNameAVIMaker<<" 2> /dev/null";
113 #else
114   aStream<<"setlocal & set P2=.;%PATH% & (for %e in (%PATHEXT%) do @for %i in ("<<myNameAVIMaker<<"%e) do @if NOT \"%~$P2:i\"==\"\" exit /b 0) & exit /b 1";
115 #endif
116   std::string anAVIMakeCheck = aStream.str();
117   int iErr = system(anAVIMakeCheck.c_str());
118   if(iErr != 0)
119     myErrorStatus = 127;
120 }
121
122
123 //----------------------------------------------------------------------------
124 void
125 SVTK_Recorder
126 ::SetName(const char* theName)
127 {
128   myName = theName;
129 }
130
131 const char*
132 SVTK_Recorder::Name() const
133 {
134   return myName.c_str();
135 }
136
137
138 //----------------------------------------------------------------------------
139 void
140 SVTK_Recorder
141 ::SetNbFPS(const double theNbFPS)
142 {
143   myNbFPS = theNbFPS;
144 }
145
146 double
147 SVTK_Recorder
148 ::NbFPS() const
149 {
150   return myNbFPS;
151 }
152
153
154 //----------------------------------------------------------------------------
155 void
156 SVTK_Recorder
157 ::SetQuality(int theQuality)
158 {
159   myQuality = theQuality;
160 }
161
162 int
163 SVTK_Recorder
164 ::GetQuality() const
165 {
166   return myQuality;
167 }
168
169
170 //----------------------------------------------------------------------------
171 void
172 SVTK_Recorder
173 ::SetRenderWindow(vtkRenderWindow* theRenderWindow)
174 {
175   myRenderWindow = theRenderWindow;
176 }
177
178 vtkRenderWindow*
179 SVTK_Recorder
180 ::RenderWindow()
181 {
182   return myRenderWindow;
183 }
184
185
186 //----------------------------------------------------------------------------
187 void
188 SVTK_Recorder
189 ::SetProgressiveMode(bool theProgressiveMode)
190 {
191   myProgressiveMode = theProgressiveMode;
192 }
193
194 bool
195 SVTK_Recorder
196 ::GetProgressiveMode() const
197 {
198   return myProgressiveMode;
199 }
200
201
202 //----------------------------------------------------------------------------
203 void
204 SVTK_Recorder
205 ::SetUseSkippedFrames(bool theUseSkippedFrames)
206 {
207   myUseSkippedFrames = theUseSkippedFrames;
208 }
209
210 bool
211 SVTK_Recorder
212 ::UseSkippedFrames() const
213 {
214   return myUseSkippedFrames;
215 }
216
217
218 //----------------------------------------------------------------------------
219 int
220 SVTK_Recorder
221 ::ErrorStatus() const
222 {
223   return myErrorStatus;
224 }
225
226 int
227 SVTK_Recorder
228 ::State() const
229 {
230   return myState;
231 }
232
233
234 //----------------------------------------------------------------------------
235 void
236 SVTK_Recorder
237 ::ProcessEvents(vtkObject* vtkNotUsed(theObject),
238                 unsigned long theEvent,
239                 void* theClientData,
240                 void* vtkNotUsed(theCallData))
241 {
242   if(vtkObject* anObj = reinterpret_cast<vtkObject*>(theClientData)){
243     if(SVTK_Recorder* aSelf = dynamic_cast<SVTK_Recorder*>(anObj)){
244       if(theEvent==vtkCommand::EndEvent){
245         if(aSelf->State() == SVTK_Recorder::SVTK_Recorder_Record){
246           aSelf->DoRecord();
247         }
248       }
249     }
250   }
251 }
252
253
254 //----------------------------------------------------------------------------
255 void
256 SVTK_Recorder
257 ::Record()
258 {
259   if(myState == SVTK_Recorder_Stop){
260     if(myRenderWindow){
261       myState = SVTK_Recorder_Record;
262       myFilter->SetInput(myRenderWindow);
263       myFrameIndex = -1;
264       myNbWrittenFrames = 0;
265       myRenderWindow->RemoveObserver(myCommand);
266       myRenderWindow->AddObserver(vtkCommand::EndEvent,
267                                   myCommand,
268                                   myPriority);
269       myRenderWindow->Render();
270     }
271   }
272 }
273
274
275 //----------------------------------------------------------------------------
276 void
277 SVTK_Recorder
278 ::Stop()
279 {
280   QApplication::setOverrideCursor( Qt::WaitCursor );
281
282   if(myState == SVTK_Recorder_Record){
283     if(!myPaused)
284       DoRecord();
285
286     myWriterMgr->Stop();
287
288     if(myUseSkippedFrames)
289       AddSkippedFrames();
290
291     myFrameIndexes.clear();
292
293     MakeFileAVI();
294   }
295   myState = SVTK_Recorder_Stop;
296   myPaused = 0;
297
298   QApplication::restoreOverrideCursor();
299 }
300
301
302 //----------------------------------------------------------------------------
303 void
304 SVTK_Recorder
305 ::Pause()
306 {
307   myPaused = myPaused ? 0 : 1;
308   if(myPaused && !myFrameIndexes.empty()){
309     myFrameIndexes.back() *= -1;
310
311     if(SALOME::VerbosityActivated())
312       cout << "SVTK_Recorder::Pause - myFrameIndexes.back() = " << myFrameIndexes.back() << endl;
313   }
314 }
315
316
317 //----------------------------------------------------------------------------
318 inline
319 int
320 GetFrameIndex(double theStartTime,
321               double theFPS)
322 {
323   double aTimeNow = vtkTimerLog::GetUniversalTime();
324   double aDelta = aTimeNow - theStartTime;
325   return int(aDelta*theFPS);
326 }
327
328 void
329 SVTK_Recorder
330 ::DoRecord()
331 {
332   if(myPaused)
333     return;
334
335   if(myFrameIndex < 0){
336     myFrameIndex = 0;
337     myTimeStart = vtkTimerLog::GetUniversalTime();
338   }else{
339     int aFrameIndex = GetFrameIndex(myTimeStart,myNbFPS);
340     if(aFrameIndex <= myFrameIndex)
341       return;
342
343     // If there was a "pause" we correct the myTimeStart
344     int aLastFrameIndex = myFrameIndexes.back();
345     if(aLastFrameIndex < 0){
346       myFrameIndexes.back() = abs(myFrameIndexes.back());
347       double aPauseTime = fabs((double)(aFrameIndex - myFrameIndex - 1)) / myNbFPS;
348
349       if(SALOME::VerbosityActivated())
350         cout << "SVTK_Recorder::DoRecord - aFrameIndex = " << aFrameIndex <<
351         "; aPauseTime = " << aPauseTime << endl;
352
353       myTimeStart += aPauseTime;
354     }
355
356     aFrameIndex = GetFrameIndex(myTimeStart,myNbFPS);
357     if(aFrameIndex <= myFrameIndex)
358       return;
359
360     myFrameIndex = aFrameIndex;
361   }
362
363   myFrameIndexes.push_back(myFrameIndex);
364   if(SALOME::VerbosityActivated())
365     cout << "SVTK_Recorder::DoRecord - myFrameIndex = " << myFrameIndex << endl;
366
367   myRenderWindow->RemoveObserver(myCommand);
368   myFilter->Modified();
369
370   std::string aName;
371   GetNameJPEG(myName,myFrameIndex,aName);
372
373   PreWrite();
374
375   vtkImageData *anImageData = vtkImageData::New();
376   anImageData->DeepCopy(myFilter->GetOutput());
377
378   myWriterMgr->StartImageWriter(myFilter,anImageData,aName,myProgressiveMode,myQuality);
379   myNbWrittenFrames++;
380
381   myRenderWindow->AddObserver(vtkCommand::EndEvent,
382                               myCommand,
383                               myPriority);
384 }
385
386
387 //----------------------------------------------------------------------------
388 void
389 SVTK_Recorder
390 ::PreWrite()
391 {
392   vtkImageData *anImageData = myFilter->GetOutput();
393   //
394   if(!anImageData){
395     myErrorStatus = 20;
396     return;
397   }
398   myFilter->UpdateInformation();
399   myFilter->UpdateWholeExtent();
400 }
401
402
403 //----------------------------------------------------------------------------
404 void
405 SVTK_Recorder
406 ::AddSkippedFrames()
407 {
408   myErrorStatus = 0;
409
410   if(myFrameIndexes.size() < 2)
411     return;
412
413   size_t anId = 0, anEnd = myFrameIndexes.size() - 1;
414   for(; anId < anEnd; anId++){
415     int aStartIndex = myFrameIndexes[anId];
416     if(aStartIndex < 0)
417       continue;
418
419     int aFinishIndex = abs(myFrameIndexes[anId + 1]);
420     if(aStartIndex + 1 == aFinishIndex)
421       continue;
422
423     std::string anInitialName;
424     std::ostringstream aStream;
425     GetNameJPEG(myName,aStartIndex,anInitialName);
426     for(int anIndex = aStartIndex + 1; anIndex < aFinishIndex; anIndex++){
427       myNbWrittenFrames++;
428       std::string anCurrentName;
429       GetNameJPEG(myName,anIndex,anCurrentName);
430   #ifndef WIN32
431       aStream<<"ln -s "<< anInitialName<<" "<<anCurrentName<<";";
432   #else
433       aStream<<"COPY /Y "<<QString::fromStdString(anInitialName).replace("/","\\\\").toStdString()<<
434                   " "<<QString::fromStdString(anCurrentName).replace("/","\\\\").toStdString()<<" > NUL";
435   #endif
436       if(anIndex + 1 < aFinishIndex) {
437   #ifndef WIN32
438         aStream<<" \\";
439         aStream<<endl;
440   #else
441         aStream<<" & ";
442   #endif
443       }
444     }
445     std::string aString(aStream.str());
446     system(aString.c_str());
447     if(SALOME::VerbosityActivated())
448       cout << "SVTK_Recorder::AddSkippedFrames - " << aString << endl;
449   }
450 }
451
452
453 //----------------------------------------------------------------------------
454 void
455 SVTK_Recorder
456 ::MakeFileAVI()
457 {
458   myErrorStatus = 0;
459   std::ostringstream aStream;
460   aStream<<myNameAVIMaker<<
461     " -I p"<<
462     " -v 0"<<
463     //" -f "<<int(myNbFPS)<<" "<<
464     " -f "<<myNbFPS<<" "<<
465     " -n "<<myNbWrittenFrames<<" "<<
466     " -j \""<<myName<<"_%06d.jpeg\" "<<
467     "| yuv2lav"<<" -o \""<<myName<<"\"";
468 #ifdef WIN32
469   aStream<<" -f aA";
470 #endif
471   std::string aString(aStream.str());
472   myErrorStatus = system(aString.c_str());
473
474   if(SALOME::VerbosityActivated())
475     cout << "SVTK_Recorder::MakeFileAVI - " << aString << endl;
476
477   QFileInfo aFileInfo(myName.c_str());
478   QString aDirPath = aFileInfo.absoluteDir().path();
479   QString aBaseName = aFileInfo.fileName();
480   QString aCommand;
481 #ifndef WIN32
482   aCommand = QString("(cd ") + aDirPath +
483     "; ls " +
484     " | egrep '" + aBaseName + "_[0-9]*.jpeg'" +
485     " | xargs rm " +
486     ")";
487 #else
488   QString tmpFile = QString("_") + aBaseName + "_tempfile";
489   QString diskName = aDirPath.split("/")[0];
490   aCommand = diskName + " && (cd " + aDirPath.replace("/","\\\\") +
491         " && ((dir /b | findstr " + aBaseName + "_[0-9]*.jpeg > " + tmpFile +
492         ") & (for /f %i in (" + tmpFile + ") do (del \"%i\")) & (del " + tmpFile + "))) > NUL";
493 #endif
494
495   if(SALOME::VerbosityActivated())
496     cout << "SVTK_Recorder::MakeFileAVI - " << (const char*)aCommand.toUtf8() << endl;
497     
498   system((const char*)aCommand.toUtf8());
499 }