1 // processor.cpp: Flash movie processor (gprocessor command), for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
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.
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 #include "gnashconfig.h"
30 #include <boost/any.hpp>
36 #include "NullSoundHandler.h"
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"
47 #include "GnashException.h"
49 #include "noseek_fd_adapter.h"
50 #include "ManualClock.h"
51 #include "StringPredicates.h"
52 #include "smart_ptr.h"
53 #include "IOChannel.h" // for proper dtor call
54 #include "GnashSleep.h" // for usleep comptibility.
55 #include "StreamProvider.h"
56 #include "RunResources.h"
57 #include "HostInterface.h"
61 #include "Renderer_agg.h"
71 extern int optind
, getopt(int, char *const *, const char *);
75 #ifdef BOOST_NO_EXCEPTIONS
79 void throw_exception(std::exception
const & e
)
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 *);
111 gnash::LogFile
& dbglogfile
= gnash::LogFile::getDefaultInstance();
112 gnash::RcInitFile
& rcfile
= gnash::RcInitFile::getDefaultInstance();
115 static bool play_movie(const std::string
& filename
,
116 const RunResources
& runResources
);
118 static bool s_stop_on_errors
= true;
120 // How many time do we allow to hit the end ?
121 static size_t allowed_end_hits
= 1;
123 double lastAdvanceTimer
;
126 resetLastAdvanceTimer()
128 // clocktime::getTicks() returns milliseconds
129 lastAdvanceTimer
= static_cast<double>(clocktime::getTicks()) / 1000.0;
133 secondsSinceLastAdvance()
135 // clocktime::getTicks() returns milliseconds
136 double now
= static_cast<double>(clocktime::getTicks()) / 1000.0;
137 return ( now
- lastAdvanceTimer
);
140 // A flag which will be used to interrupt playback
141 // by effect of a "quit" fscommand
143 static int quitrequested
= false;
145 class FsCommandExecutor
: public FsCallback
{
147 void notify(const std::string
& command
, const std::string
& args
)
149 log_debug(_("fs_callback(%p): %s %s"), command
, args
);
151 StringNoCaseEqual ncasecomp
;
153 if ( ncasecomp(command
, "quit") ) quitrequested
= true;
157 class EventCallback
: public HostInterface
160 boost::any
call(const HostInterface::Message
& e
)
162 if (e
.type() != typeid(HostMessage
)) return boost::blank();
164 const HostMessage
& ev
= boost::get
<HostMessage
>(e
);
165 const HostMessage::KnownEvent event
= ev
.event();
167 log_debug(_("eventCallback: %s %s"), event
);
169 static bool mouseShown
= true;
170 static std::string clipboard
;
173 case HostMessage::QUERY
:
175 case HostMessage::SET_CLIPBOARD
:
176 clipboard
= boost::any_cast
<std::string
>(ev
.arg());
177 return boost::blank();
179 case HostMessage::SHOW_MOUSE
:
181 bool state
= mouseShown
;
182 mouseShown
= boost::any_cast
<bool>(ev
.arg());
186 // Some fake values for consistent test results.
187 case HostMessage::SCREEN_RESOLUTION
:
188 return std::make_pair(800, 640);
190 case HostMessage::SCREEN_DPI
:
193 case HostMessage::SCREEN_COLOR
:
194 return std::string("Color");
196 case HostMessage::PLAYER_TYPE
:
197 return std::string("StandAlone");
199 case HostMessage::PIXEL_ASPECT_RATIO
:
203 log_debug(_("gprocessor does not handle %1% message"), e
);
207 return boost::blank();
210 virtual void exit() {
211 std::exit(EXIT_SUCCESS
);
215 EventCallback eventCallback
;
216 FsCommandExecutor execFsCommand
;
219 main(int argc
, char *argv
[])
221 std::ios::sync_with_stdio(false);
223 // Enable native language support, i.e. internationalization
225 std::setlocale (LC_ALL
, "");
226 bindtextdomain (PACKAGE
, LOCALEDIR
);
227 textdomain (PACKAGE
);
231 // scan for the two main standard GNU options
232 for (c
= 0; c
< argc
; c
++) {
233 if (strcmp("--help", argv
[c
]) == 0) {
237 if (strcmp("--version", argv
[c
]) == 0) {
238 printf (_("Gnash gprocessor version: %s, Gnash version: %s\n"),
239 GPROC_VERSION
, VERSION
);
244 std::vector
<std::string
> infiles
;
246 //RcInitFile& rcfile = RcInitFile::getDefaultInstance();
247 //rcfile.loadFiles();
249 if (rcfile
.verbosityLevel() > 0) {
250 dbglogfile
.setVerbosity(rcfile
.verbosityLevel());
253 dbglogfile
.setLogFilename(rcfile
.getDebugLog());
255 if (rcfile
.useWriteLog()) {
256 dbglogfile
.setWriteDisk(true);
259 if (rcfile
.useActionDump()) {
260 dbglogfile
.setActionDump(true);
261 dbglogfile
.setVerbosity();
264 if (rcfile
.useParserDump()) {
265 dbglogfile
.setParserDump(true);
266 dbglogfile
.setVerbosity();
269 while ((c
= getopt (argc
, argv
, ":hvapr:gf:d:n")) != -1) {
273 dbglogfile
.removeLog();
276 dbglogfile
.setVerbosity();
277 log_debug (_("Verbose output turned on"));
280 dbglogfile
.setNetwork(true);
284 dbglogfile
.setActionDump(true);
286 log_error (_("Verbose actions disabled at compile time"));
291 dbglogfile
.setParserDump(true);
293 log_error (_("Verbose parsing disabled at compile time"));
297 allowed_end_hits
= strtol(optarg
, NULL
, 0);
300 delay
= strtol(optarg
, NULL
, 0)*1000; // delay is in microseconds
301 // this will be recognized as a request to run at FPS speed
302 if ( delay
< 0 ) delay
= -1;
305 limit_advances
= strtol(optarg
, NULL
, 0);
308 fprintf(stderr
, "Missing argument for switch ``%c''\n", optopt
);
312 fprintf(stderr
, "Unknown switch ``%c''\n", optopt
);
318 // get the file name from the command line
319 while (optind
< argc
) {
320 infiles
.push_back(argv
[optind
]);
324 // No file names were supplied
325 if (infiles
.empty()) {
326 std::cerr
<< "no input files" << std::endl
;
328 dbglogfile
.removeLog();
332 boost::shared_ptr
<gnash::media::MediaHandler
> mediaHandler
;
333 boost::shared_ptr
<sound::sound_handler
> soundHandler
;
335 std::string mh
= rcfile
.getMediaHandler();
336 mediaHandler
.reset(media::MediaFactory::instance().get(mh
));
337 soundHandler
.reset(new sound::NullSoundHandler(mediaHandler
.get()));
341 boost::shared_ptr
<SWF::TagLoadersTable
> loaders(new SWF::TagLoadersTable());
342 addDefaultLoaders(*loaders
);
345 boost::shared_ptr
<Renderer_agg_base
> r(create_Renderer_agg("RGBA32"));
347 // Yes, this leaks. On some systems (e.g. Debian Lenny) the data is
348 // evidently accessed after main() returns. Rather than bothering to
349 // work out why, we let this byte leak, as it's returned to the system on
351 unsigned char* buf
= new unsigned char[8];
352 r
->init_buffer(buf
, 1, 1, 1, 1);
355 // Play through all the movies.
356 for (std::vector
<std::string
>::const_iterator i
= infiles
.begin(),
357 e
= infiles
.end(); i
!= e
; ++i
)
360 RunResources runResources
;
361 runResources
.setSoundHandler(soundHandler
);
362 runResources
.setMediaHandler(mediaHandler
);
363 runResources
.setTagLoaders(loaders
);
364 boost::shared_ptr
<StreamProvider
> sp(new StreamProvider(*i
, *i
));
365 runResources
.setStreamProvider(sp
);
368 runResources
.setRenderer(r
);
371 bool success
= play_movie(*i
, runResources
);
373 if (s_stop_on_errors
) {
375 std::cerr
<< "error playing through movie " << *i
<< std::endl
;
376 std::exit(EXIT_FAILURE
);
385 // Load the named movie, make an instance, and play it, virtually.
386 // I.e. run through and render all the frames, even though we are not
387 // actually doing any output (our output handlers are disabled).
390 play_movie(const std::string
& filename
, const RunResources
& runResources
)
392 boost::intrusive_ptr
<gnash::movie_definition
> md
;
394 quitrequested
= false;
402 std::auto_ptr
<IOChannel
> in (
403 noseek_fd_adapter::make_stream(fileno(stdin
)) );
404 md
= MovieFactory::makeMovie(in
, filename
, runResources
, false);
408 if ( url
.protocol() == "file" )
410 const std::string
& path
= url
.path();
411 #if 1 // add the *directory* the movie was loaded from to the local sandbox path
412 size_t lastSlash
= path
.find_last_of('/');
413 std::string dir
= path
.substr(0, lastSlash
+1);
414 rcfile
.addLocalSandboxPath(dir
);
415 log_debug(_("%s appended to local sandboxes"), dir
.c_str());
416 #else // add the *file* to be loaded to the local sandbox path
417 rcfile
.addLocalSandboxPath(path
);
418 log_debug(_("%s appended to local sandboxes"), path
.c_str());
421 md
= MovieFactory::makeMovie(url
, runResources
, NULL
, false);
424 catch (GnashException
& ge
)
427 fprintf(stderr
, "%s\n", ge
.what());
430 std::cerr
<< "error: can't play movie: "<< filename
<< std::endl
;
434 float fps
= md
->get_frame_rate();
435 long fpsDelay
= long(1000000/fps
);
436 long clockAdvance
= fpsDelay
/1000;
437 long localDelay
= delay
== -1 ? fpsDelay
: delay
; // microseconds
439 log_debug("Will sleep %ld microseconds between iterations - "
440 "fps is %g, clockAdvance is %lu", localDelay
, fps
, clockAdvance
);
443 // Use a clock advanced at every iteration to match exact FPS speed.
445 gnash::movie_root
m(*md
, cl
, runResources
);
447 // Register processor to receive ActionScript events (Mouse, Stage
449 m
.registerEventCallback(&eventCallback
);
450 m
.registerFSCommandCallback(&execFsCommand
);
454 MovieClip::MovieVariables v
;
457 log_debug("iteration, timer: %lu, localDelay: %ld\n",
458 cl
.elapsed(), localDelay
);
459 gnashSleep(localDelay
);
461 resetLastAdvanceTimer();
464 size_t loop_back_count
=0;
465 size_t latest_frame
=0;
466 size_t end_hitcount
=0;
468 // Run through the movie.
469 while (!quitrequested
) {
471 size_t last_frame
= m
.get_current_frame();
472 //printf("advancing clock by %lu\n", clockAdvance);
473 cl
.advance(clockAdvance
);
478 quitrequested
= false;
482 m
.display(); // FIXME: for which reason are we calling display here ??
484 if ( limit_advances
&& nadvances
>= limit_advances
)
486 log_debug("exiting after %d advances", nadvances
);
490 size_t curr_frame
= m
.get_current_frame();
492 // We reached the end, done !
493 if (curr_frame
>= md
->get_frame_count() - 1 )
495 if ( allowed_end_hits
&& ++end_hitcount
>= allowed_end_hits
)
497 log_debug("exiting after %d"
498 " times last frame was reached", end_hitcount
);
504 if (curr_frame
== last_frame
)
506 // Max stop counts reached, kick it
507 if ( secondsSinceLastAdvance() > waitforadvance
)
512 if ( last_frame
+ 1 > md
->get_frame_count() -1 )
514 fprintf(stderr
, "Exiting after %g seconds in STOP mode at last frame\n", waitforadvance
);
517 fprintf(stderr
, "Kicking movie after %g seconds in STOP mode, kick ct = %d\n", waitforadvance
, kick_count
);
519 m
.goto_frame(last_frame
+ 1);
520 m
.getRootMovie().setPlayState(gnash::MovieClip::PLAYSTATE_PLAY
);
523 if (kick_count
> 10) {
524 printf("movie is stalled; giving up on playing it through.\n");
528 resetLastAdvanceTimer(); // It's like we advanced
532 // We looped back. Skip ahead...
533 else if (m
.get_current_frame() < last_frame
)
535 if ( last_frame
> latest_frame
) latest_frame
= last_frame
;
536 if ( ++loop_back_count
> allowloopbacks
)
538 log_debug("%d loop backs; jumping one-after "
540 loop_back_count
, latest_frame
+1);
541 m
.goto_frame(latest_frame
+ 1);
549 resetLastAdvanceTimer();
552 log_debug("iteration, timer: %lu, localDelay: %ld\n",
553 cl
.elapsed(), localDelay
);
554 gnashSleep(localDelay
);
557 log_debug("-- Playback completed");
559 log_debug("-- Dropping ref of movie_definition");
561 // drop reference to movie_definition, to force
562 // destruction when core gnash doesn't need it anymore
565 log_debug("-- Cleaning gnash");
568 // Forces run of GC, which in turn may invoke
569 // destuctors of (say) MovieClip which would try
570 // to access the movie_root to unregister self
572 // This means that movie_root must be available.
573 MovieFactory::clear();
579 usage (const char *name
)
582 _("gprocessor -- an SWF processor for Gnash.\n"
584 "usage: %s [options] <file>\n"
586 "Process the given SWF movie files.\n"
588 "%s%s%s%s"), name
, _(
591 " --help(-h) Print this info.\n"
592 " --version Print the version numbers.\n"
593 " -v Be verbose; i.e. print log messages to stdout\n"
596 _(" -vp Be verbose about movie parsing\n"),
601 _(" -va Be verbose about ActionScript\n"),
607 " Milliseconds delay between advances (0 by default).\n"
608 " If '-1' the delay will be computed from the FPS.\n"
609 " -r <times> Allow the given number of complete runs.\n"
610 " Keep looping undefinitely if set to 0.\n"
611 " Default is 1 (end as soon as the last frame is reached).\n"
613 " Allow the given number of frame advancements.\n"
614 " Keep advancing untill any other stop condition\n"
615 " is encountered if set to 0 (default).\n")
622 // indent-tabs-mode: t