Move "loop" related tests in their own dir. This is just to break the ice... ideally...
[gnash.git] / utilities / processor.cpp
blob3d3bd73542c68189e6e5ea53384ca9582dd69c3b
1 // processor.cpp: Flash movie processor (gprocessor command), for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
25 #include "NullSoundHandler.h"
27 #include <ios>
28 #include <iostream>
29 #include <cstdio>
30 #include <cstdlib>
31 #include <ctime>
33 #ifdef ENABLE_NLS
34 # include <clocale>
35 #endif
37 #include "MovieFactory.h"
38 #include "swf/TagLoadersTable.h"
39 #include "swf/DefaultTagLoaders.h"
40 #include "ClockTime.h"
41 #include "movie_definition.h"
42 #include "MovieClip.h"
43 #include "movie_root.h"
44 #include "log.h"
45 #include "rc.h"
46 #include "URL.h"
47 #include "GnashException.h"
48 #include "debugger.h"
49 #include "VM.h"
50 #include "noseek_fd_adapter.h"
51 #include "ManualClock.h"
52 #include "StringPredicates.h"
53 #include "smart_ptr.h"
54 #include "IOChannel.h" // for proper dtor call
55 #include "GnashSleep.h" // for usleep comptibility.
56 #include "StreamProvider.h"
57 #include "RunResources.h"
59 #ifdef RENDERER_AGG
60 #include "Renderer.h"
61 #include "Renderer_agg.h"
62 #endif
64 extern "C"{
65 #ifdef HAVE_GETOPT_H
66 #include <getopt.h>
67 #endif
68 #ifndef __GNUC__
69 extern char *optarg;
70 extern int optopt;
71 extern int optind, getopt(int, char *const *, const char *);
72 #endif
75 #ifdef BOOST_NO_EXCEPTIONS
77 namespace boost
79 void throw_exception(std::exception const & e)
81 std::abort();
84 #endif
86 // How many seconds to wait for a frame advancement
87 // before kicking the movie (forcing it to next frame)
88 static const double waitforadvance = 5;
90 // How many time do we allow for loop backs
91 // (goto frame < current frame)
92 static const size_t allowloopbacks = 10;
94 // How many times to call 'advance' ?
95 // If 0 number of advance is unlimited
96 // (see other constraints)
97 // TODO: add a command-line switch to control this
98 static size_t limit_advances = 0;
100 // How much time to sleep between advances ?
101 // If set to -1 it will be computed based on FPS.
102 static long int delay = 0;
104 const char *GPROC_VERSION = "1.0";
106 using namespace gnash;
108 static void usage (const char *);
110 namespace {
111 gnash::LogFile& dbglogfile = gnash::LogFile::getDefaultInstance();
112 gnash::RcInitFile& rcfile = gnash::RcInitFile::getDefaultInstance();
113 #ifdef USE_DEBUGGER
114 gnash::Debugger& debugger = gnash::Debugger::getDefaultInstance();
115 #endif
118 static bool play_movie(const std::string& filename,
119 const RunResources& runResources);
121 static bool s_stop_on_errors = true;
123 // How many time do we allow to hit the end ?
124 static size_t allowed_end_hits = 1;
126 double lastAdvanceTimer;
128 void
129 resetLastAdvanceTimer()
131 // clocktime::getTicks() returns milliseconds
132 lastAdvanceTimer = static_cast<double>(clocktime::getTicks()) / 1000.0;
135 double
136 secondsSinceLastAdvance()
138 // clocktime::getTicks() returns milliseconds
139 double now = static_cast<double>(clocktime::getTicks()) / 1000.0;
140 return ( now - lastAdvanceTimer);
143 // A flag which will be used to interrupt playback
144 // by effect of a "quit" fscommand
146 static int quitrequested = false;
148 class FsCommandExecutor: public movie_root::AbstractFsCallback {
149 public:
150 void notify(const std::string& command, const std::string& args)
152 log_debug(_("fs_callback(%p): %s %s"), command, args);
154 StringNoCaseEqual ncasecomp;
156 if ( ncasecomp(command, "quit") ) quitrequested = true;
160 class EventCallback: public movie_root::AbstractIfaceCallback
162 public:
163 std::string call(const std::string& event, const std::string& arg)
165 log_debug(_("eventCallback: %s %s"), event, arg);
167 static bool mouseShown = true;
169 // These should return "true" if the mouse was visible before
170 // the call.
171 if ( event == "Mouse.hide" ) {
172 bool state = mouseShown;
173 mouseShown = false;
174 return state ? "true" : "false";
177 if ( event == "Mouse.show" ) {
178 bool state = mouseShown;
179 mouseShown = true;
180 return state ? "true" : "false" ;
183 // Some fake values for consistent test results.
185 if ( event == "System.capabilities.screenResolutionX" ) {
186 return "800";
189 if ( event == "System.capabilities.screenResolutionY" ) {
190 return "640";
193 if ( event == "System.capabilities.screenDPI" ) {
194 return "72";
197 if ( event == "System.capabilities.screenColor" ) {
198 return "Color";
201 if ( event == "System.capabilities.playerType" ) {
202 return "StandAlone";
205 return "";
209 bool yesNo(const std::string& /*query*/)
211 return true;
214 void exit() {
215 std::exit(EXIT_SUCCESS);
219 EventCallback eventCallback;
220 FsCommandExecutor execFsCommand;
223 main(int argc, char *argv[])
225 std::ios::sync_with_stdio(false);
227 // Enable native language support, i.e. internationalization
228 #ifdef ENABLE_NLS
229 std::setlocale (LC_ALL, "");
230 bindtextdomain (PACKAGE, LOCALEDIR);
231 textdomain (PACKAGE);
232 #endif
233 int c;
235 // scan for the two main standard GNU options
236 for (c = 0; c < argc; c++) {
237 if (strcmp("--help", argv[c]) == 0) {
238 usage(argv[0]);
239 exit(EXIT_SUCCESS);
241 if (strcmp("--version", argv[c]) == 0) {
242 printf (_("Gnash gprocessor version: %s, Gnash version: %s\n"),
243 GPROC_VERSION, VERSION);
244 exit(EXIT_SUCCESS);
248 std::vector<std::string> infiles;
250 //RcInitFile& rcfile = RcInitFile::getDefaultInstance();
251 //rcfile.loadFiles();
253 if (rcfile.verbosityLevel() > 0) {
254 dbglogfile.setVerbosity(rcfile.verbosityLevel());
257 dbglogfile.setLogFilename(rcfile.getDebugLog());
259 if (rcfile.useWriteLog()) {
260 dbglogfile.setWriteDisk(true);
263 if (rcfile.useActionDump()) {
264 dbglogfile.setActionDump(true);
265 dbglogfile.setVerbosity();
268 if (rcfile.useParserDump()) {
269 dbglogfile.setParserDump(true);
270 dbglogfile.setVerbosity();
273 while ((c = getopt (argc, argv, ":hvapr:gf:d:n")) != -1) {
274 switch (c) {
275 case 'h':
276 usage (argv[0]);
277 dbglogfile.removeLog();
278 exit(EXIT_SUCCESS);
279 case 'v':
280 dbglogfile.setVerbosity();
281 log_debug (_("Verbose output turned on"));
282 break;
283 case 'g':
284 #ifdef USE_DEBUGGER
285 debugger.enabled(true);
286 debugger.console();
287 log_debug (_("Setting debugger ON"));
288 #else
289 log_error (_("The debugger has been disabled at configuration time"));
290 #endif
291 case 'n':
292 dbglogfile.setNetwork(true);
293 break;
294 case 'a':
295 #if VERBOSE_ACTION
296 dbglogfile.setActionDump(true);
297 #else
298 log_error (_("Verbose actions disabled at compile time"));
299 #endif
300 break;
301 case 'p':
302 #if VERBOSE_PARSE
303 dbglogfile.setParserDump(true);
304 #else
305 log_error (_("Verbose parsing disabled at compile time"));
306 #endif
307 break;
308 case 'r':
309 allowed_end_hits = strtol(optarg, NULL, 0);
310 break;
311 case 'd':
312 delay = strtol(optarg, NULL, 0)*1000; // delay is in microseconds
313 // this will be recognized as a request to run at FPS speed
314 if ( delay < 0 ) delay = -1;
315 break;
316 case 'f':
317 limit_advances = strtol(optarg, NULL, 0);
318 break;
319 case ':':
320 fprintf(stderr, "Missing argument for switch ``%c''\n", optopt);
321 exit(EXIT_FAILURE);
322 case '?':
323 default:
324 fprintf(stderr, "Unknown switch ``%c''\n", optopt);
325 exit(EXIT_FAILURE);
330 // get the file name from the command line
331 while (optind < argc) {
332 infiles.push_back(argv[optind]);
333 optind++;
336 // No file names were supplied
337 if (infiles.empty()) {
338 std::cerr << "no input files" << std::endl;
339 usage(argv[0]);
340 dbglogfile.removeLog();
341 exit(EXIT_FAILURE);
344 boost::shared_ptr<gnash::media::MediaHandler> mediaHandler;
345 boost::shared_ptr<sound::sound_handler> soundHandler;
347 std::string mh = rcfile.getMediaHandler();
348 mediaHandler.reset(media::MediaFactory::instance().get(mh));
349 soundHandler.reset(new sound::NullSoundHandler(mediaHandler.get()));
353 boost::shared_ptr<SWF::TagLoadersTable> loaders(new SWF::TagLoadersTable());
354 addDefaultLoaders(*loaders);
356 #ifdef RENDERER_AGG
357 boost::shared_ptr<Renderer_agg_base> r(create_Renderer_agg("RGBA32"));
359 // Yes, this leaks. On some systems (e.g. Debian Lenny) the data is
360 // evidently accessed after main() returns. Rather than bothering to
361 // work out why, we let this byte leak, as it's returned to the system on
362 // exit anyway.
363 unsigned char* buf = new unsigned char[8];
364 r->init_buffer(buf, 1, 1, 1, 1);
365 #endif
367 // Play through all the movies.
368 for (std::vector<std::string>::const_iterator i = infiles.begin(),
369 e = infiles.end(); i != e; ++i)
372 RunResources runResources;
373 runResources.setSoundHandler(soundHandler);
374 runResources.setMediaHandler(mediaHandler);
375 runResources.setTagLoaders(loaders);
376 boost::shared_ptr<StreamProvider> sp(new StreamProvider(*i));
377 runResources.setStreamProvider(sp);
379 #ifdef RENDERER_AGG
380 runResources.setRenderer(r);
381 #endif
383 bool success = play_movie(*i, runResources);
384 if (!success) {
385 if (s_stop_on_errors) {
386 // Fail.
387 std::cerr << "error playing through movie " << *i << std::endl;
388 std::exit(EXIT_FAILURE);
394 return 0;
397 // Load the named movie, make an instance, and play it, virtually.
398 // I.e. run through and render all the frames, even though we are not
399 // actually doing any output (our output handlers are disabled).
401 bool
402 play_movie(const std::string& filename, const RunResources& runResources)
404 boost::intrusive_ptr<gnash::movie_definition> md;
406 quitrequested = false;
408 URL url(filename);
412 if (filename == "-")
414 std::auto_ptr<IOChannel> in (
415 noseek_fd_adapter::make_stream(fileno(stdin)) );
416 md = MovieFactory::makeMovie(in, filename, runResources, false);
418 else
420 if ( url.protocol() == "file" )
422 const std::string& path = url.path();
423 #if 1 // add the *directory* the movie was loaded from to the local sandbox path
424 size_t lastSlash = path.find_last_of('/');
425 std::string dir = path.substr(0, lastSlash+1);
426 rcfile.addLocalSandboxPath(dir);
427 log_debug(_("%s appended to local sandboxes"), dir.c_str());
428 #else // add the *file* to be loaded to the local sandbox path
429 rcfile.addLocalSandboxPath(path);
430 log_debug(_("%s appended to local sandboxes"), path.c_str());
431 #endif
433 md = MovieFactory::makeMovie(url, runResources, NULL, false);
436 catch (GnashException& ge)
438 md = NULL;
439 fprintf(stderr, "%s\n", ge.what());
441 if (md == NULL) {
442 std::cerr << "error: can't play movie: "<< filename << std::endl;
443 return false;
446 float fps = md->get_frame_rate();
447 long fpsDelay = long(1000000/fps);
448 long clockAdvance = fpsDelay/1000;
449 long localDelay = delay == -1 ? fpsDelay : delay; // microseconds
451 log_debug("Will sleep %ld microseconds between iterations - "
452 "fps is %g, clockAdvance is %lu", localDelay, fps, clockAdvance);
455 // Use a clock advanced at every iteration to match exact FPS speed.
456 ManualClock cl;
457 gnash::movie_root m(*md, cl, runResources);
459 // Register processor to receive ActionScript events (Mouse, Stage
460 // System etc).
461 m.registerEventCallback(&eventCallback);
462 m.registerFSCommandCallback(&execFsCommand);
464 md->completeLoad();
466 MovieClip::MovieVariables v;
467 m.init(md.get(), v, v);
469 log_debug("iteration, timer: %lu, localDelay: %ld\n",
470 cl.elapsed(), localDelay);
471 gnashSleep(localDelay);
473 resetLastAdvanceTimer();
474 int kick_count = 0;
475 int stop_count=0;
476 size_t loop_back_count=0;
477 size_t latest_frame=0;
478 size_t end_hitcount=0;
479 size_t nadvances=0;
480 // Run through the movie.
481 while (!quitrequested) {
482 // @@ do we also have to run through all sprite frames
483 // as well?
485 // @@ also, ActionScript can rescale things
486 // dynamically -- we can't really do much about that I
487 // guess?
489 // @@ Maybe we should allow the user to specify some
490 // safety margin on scaled shapes.
492 size_t last_frame = m.get_current_frame();
493 //printf("advancing clock by %lu\n", clockAdvance);
494 cl.advance(clockAdvance);
495 m.advance();
497 if ( quitrequested )
499 quitrequested = false;
500 break;
503 m.display(); // FIXME: for which reason are we calling display here ??
504 ++nadvances;
505 if ( limit_advances && nadvances >= limit_advances)
507 log_debug("exiting after %d advances", nadvances);
508 break;
511 size_t curr_frame = m.get_current_frame();
513 // We reached the end, done !
514 if (curr_frame >= md->get_frame_count() - 1 )
516 if ( allowed_end_hits && ++end_hitcount >= allowed_end_hits )
518 log_debug("exiting after %d"
519 " times last frame was reached", end_hitcount);
520 break;
524 // We didn't advance
525 if (curr_frame == last_frame)
527 // Max stop counts reached, kick it
528 if ( secondsSinceLastAdvance() > waitforadvance )
530 stop_count=0;
532 // Kick the movie.
533 if ( last_frame + 1 > md->get_frame_count() -1 )
535 fprintf(stderr, "Exiting after %g seconds in STOP mode at last frame\n", waitforadvance);
536 break;
538 fprintf(stderr, "Kicking movie after %g seconds in STOP mode, kick ct = %d\n", waitforadvance, kick_count);
539 fflush(stderr);
540 m.goto_frame(last_frame + 1);
541 m.set_play_state(gnash::MovieClip::PLAYSTATE_PLAY);
542 kick_count++;
544 if (kick_count > 10) {
545 printf("movie is stalled; giving up on playing it through.\n");
546 break;
549 resetLastAdvanceTimer(); // It's like we advanced
553 // We looped back. Skip ahead...
554 else if (m.get_current_frame() < last_frame)
556 if ( last_frame > latest_frame ) latest_frame = last_frame;
557 if ( ++loop_back_count > allowloopbacks )
559 log_debug("%d loop backs; jumping one-after "
560 "latest frame (%d)",
561 loop_back_count, latest_frame+1);
562 m.goto_frame(latest_frame + 1);
563 loop_back_count = 0;
566 else
568 kick_count = 0;
569 stop_count = 0;
570 resetLastAdvanceTimer();
573 log_debug("iteration, timer: %lu, localDelay: %ld\n",
574 cl.elapsed(), localDelay);
575 gnashSleep(localDelay);
578 log_debug("-- Playback completed");
580 log_debug("-- Dropping ref of movie_definition");
582 // drop reference to movie_definition, to force
583 // destruction when core gnash doesn't need it anymore
584 md = 0;
586 log_debug("-- Cleaning gnash");
588 // Clear resources.
589 // Forces run of GC, which in turn may invoke
590 // destuctors of (say) MovieClip which would try
591 // to access the movie_root to unregister self
593 // This means that movie_root must be available.
594 MovieFactory::clear();
596 return true;
599 static void
600 usage (const char *name)
602 printf(
603 _("gprocessor -- an SWF processor for Gnash.\n"
604 "\n"
605 "usage: %s [options] <file>\n"
606 "\n"
607 "Process the given SWF movie files.\n"
608 "\n"
609 "%s%s%s%s"), name, _(
610 "options:\n"
611 "\n"
612 " --help(-h) Print this info.\n"
613 " --version Print the version numbers.\n"
614 " -v Be verbose; i.e. print log messages to stdout\n"
616 #if VERBOSE_PARSE
617 _(" -vp Be verbose about movie parsing\n"),
618 #else
620 #endif
621 #if VERBOSE_ACTION
622 _(" -va Be verbose about ActionScript\n"),
623 #else
625 #endif
627 " -d [<ms>]\n"
628 " Milliseconds delay between advances (0 by default).\n"
629 " If '-1' the delay will be computed from the FPS.\n"
630 " -r <times> Allow the given number of complete runs.\n"
631 " Keep looping undefinitely if set to 0.\n"
632 " Default is 1 (end as soon as the last frame is reached).\n"
633 " -f <frames> \n"
634 " Allow the given number of frame advancements.\n"
635 " Keep advancing untill any other stop condition\n"
636 " is encountered if set to 0 (default).\n")
641 // Local Variables:
642 // mode: C++
643 // indent-tabs-mode: t
644 // End: