File Coverage

blib/lib/BeamerReveal/MediaManager.pm
Criterion Covered Total %
statement 66 419 15.7
branch 0 66 0.0
condition n/a
subroutine 22 48 45.8
pod 14 19 73.6
total 102 552 18.4


line stmt bran cond sub pod time code
1             # -*- cperl -*-
2             # ABSTRACT: MediaManager
3              
4              
5             package BeamerReveal::MediaManager;
6             our $VERSION = '20260208.1851'; # VERSION
7              
8 1     1   2479 use strict;
  1         3  
  1         45  
9 1     1   6 use warnings;
  1         2  
  1         62  
10              
11 1     1   7 use Carp;
  1         2  
  1         92  
12 1     1   1574 use Env;
  1         3784  
  1         6  
13              
14 1     1   784 use POSIX;
  1         2  
  1         11  
15              
16 1     1   4154 use File::Which;
  1         2  
  1         67  
17 1     1   7 use File::Path;
  1         13  
  1         63  
18 1     1   622 use File::Copy;
  1         4500  
  1         74  
19 1     1   8 use File::Basename;
  1         2  
  1         101  
20              
21 1     1   562 use Data::UUID;
  1         1259  
  1         86  
22              
23 1     1   781 use MIME::Types;
  1         6931  
  1         66  
24 1     1   735 use MIME::Base64;
  1         1100  
  1         82  
25              
26 1     1   8 use IO::File;
  1         3  
  1         194  
27              
28 1     1   811 use MCE::Hobo;
  1         73919  
  1         6  
29 1     1   71 use MCE::Util;
  1         4  
  1         71  
30 1     1   618 use MCE::Shared::Scalar;
  1         1116  
  1         57  
31              
32 1     1   9 use Time::HiRes;
  1         2  
  1         8  
33              
34 1     1   701 use BeamerReveal::TemplateStore;
  1         4  
  1         67  
35 1     1   7 use BeamerReveal::IPC::Run;
  1         27  
  1         41  
36 1     1   6 use IPC::Run qw(harness start pump finish);
  1         2  
  1         100  
37              
38 1     1   8 use File::chdir;
  1         2  
  1         150  
39              
40 1     1   8 use BeamerReveal::Log;
  1         2  
  1         6897  
41              
42 0     0 0   sub min { $_[$_[0] > $_[1] ] }
43 0     0 0   sub nofdigits { length( "$_[0]" ) }
44              
45              
46             sub new {
47 0     0 1   my $class = shift;
48 0           my ( $jobname, $odir, $base, $presentationprms, $debug ) = @_;
49              
50 0           my $self = {
51             jobname => $jobname,
52             outputdir => $odir,
53             base => $base,
54             debug => $debug,
55             };
56 0 0         $class = (ref $class ? ref $class : $class );
57 0           bless $self, $class;
58              
59 0           $self->{presentationparameters} = $presentationprms;
60              
61             ########################
62             # Prepare all the paths
63              
64 0           $self->{videos} = "$self->{base}/media/Videos";
65 0           $self->{audios} = "$self->{base}/media/Audios";
66 0           $self->{images} = "$self->{base}/media/Images";
67 0           $self->{animations} = "$self->{base}/media/Animations";
68 0           $self->{stills} = "$self->{base}/media/Stills";
69 0           $self->{iframes} = "$self->{base}/media/Iframes";
70 0           $self->{slides} = "$self->{base}/media/Slides";
71 0           $self->{reveal} = "$self->{base}/libs";
72 0           $self->{mtoracle} = MIME::Types->new();
73            
74             # put ourselves in the output directory for now
75             {
76 0           local $CWD = $self->{outputdir};
  0            
77            
78             # create animation path, but don't remove contents
79 0           for my $item ( qw(animations stills) ) {
80 0           File::Path::make_path( $self->{$item} );
81             }
82              
83             # create all the ohter paths and also clean them
84 0           for my $item ( qw(reveal videos audios iframes images) ) {
85 0           File::Path::rmtree( $self->{$item} );
86 0           File::Path::make_path( $self->{$item} );
87             }
88             }
89              
90             # read the relevant part of the preamble of the job
91 0           my $texFileName = $self->{jobname};
92 0           my $realTexFileName;
93 0           foreach my $ext ( qw(tex ltx latex) ) {
94 0           $realTexFileName = $texFileName . ".$ext";
95 0 0         last if ( -r $realTexFileName );
96             }
97              
98 0           my $logger = $BeamerReveal::Log::logger;
99            
100 0           my $texFile = IO::File->new();
101 0 0         $texFile->open( "<$realTexFileName" )
102             or $logger->fatal( "Error: could not open your original LaTeX source file '$realTexFileName'\n" );
103 0           $self->{preamble} = '';
104 0           my $line;
105 0           do { $line = <$texFile> } until( $line =~ /\\usepackage[^{]*{beamer-reveal}/ );
  0            
106 0           $line = "%% Preamble excerpt taken from $realTexFileName\n";
107 0           until ( $line =~ /\\begin\{document\}/ ) {
108 0           $self->{preamble} .= $line;
109 0           $line = <$texFile>;
110             }
111 0           $texFile->close();
112 0           $self->{preamble} .= "%% End of preamble excerpt\n";
113              
114             ####################################################
115             # check all the preconditions for running our tools
116             $self->{compiler} = File::Which::which( $self->{presentationparameters}->{compiler} )
117 0 0         or $logger->fatal( "Error: your setup is incomplete, I cannot find your $self->{presentationparameters}->{compiler } compiler (should be part of your TeX installation)\n" .
118             "Make sure it is accessible in a directory on your PATH list variable\n" );
119            
120 0 0         $self->{pdftoppm} = File::Which::which( 'pdftoppm' )
121             or $logger->fatal( "Error: your setup is incomplete, I cannot find pdftoppm (part of the poppler library)\n" .
122             "Install 'Poppler-utils' and make sure pdftoppm is accessible in a directory on your PATH list variable\n" );
123              
124 0 0         $self->{pdfcrop} = File::Which::which( 'pdfcrop' )
125             or $logger->fatal( "Error: your setup is incomplete, I cannot find pdfcrop (should be part of your TeX installation)\n" .
126             "Make sure it is accessible in a directory on your PATH list variable\n" );
127 0 0         $self->{ffmpeg} = File::Which::which( 'ffmpeg' )
128             or $logger->fatal( "Error: your setup is incomplete, I cannot find ffmpeg\n" .
129             "Install 'FFmpeg' (from www.ffmpeg.org) and make sure ffmpeg is accessible in a directory on your PATH list variable\n" );
130              
131             ##########################################################################
132             # We sell things, before we make the, so we need to keep a backorder list
133              
134 0           $self->{copyBackOrders} = [];
135 0           $self->{constructionBackOrders} = { 'animations' => {},
136             'stills' => {}
137             };
138              
139 0           return $self;
140             }
141              
142              
143             sub revealToStore {
144 0     0 1   my $self = shift;
145              
146 0           my $revealTree = File::ShareDir::dist_dir( 'BeamerReveal' ) . '/libs';
147 0           my $destTree = $self->{reveal};
148              
149             File::Copy::Recursive::dircopy( $revealTree,
150 0           $self->{outputdir} . '/' . $destTree );
151             }
152              
153              
154             sub backgroundsToStore() {
155 0     0 1   my $self = shift;
156 0           my ( $bgs ) = @_;
157 0           $self->{backgrounds} = $bgs;
158             }
159              
160              
161             sub notesToStore() {
162 0     0 1   my $self = shift;
163 0           my ( $notes ) = @_;
164 0           $self->{notes} = $notes;
165             }
166              
167              
168             sub slideFromStore {
169 0     0 1   my $self = shift;
170 0           my ( $slidectr, %optargs ) = @_;
171            
172 0           my $logger = $BeamerReveal::Log::logger;
173            
174 0           my $fileName = $self->{backgrounds}->[$slidectr];
175 0           my $fileType = $self->{mtoracle}->mimeTypeOf( $fileName );
176            
177 0 0         if( exists $optargs{to_embed} ) {
178 0           my $file = do {
179 0           local $/ = undef;
180 0 0         open my $fh, '<' . $self->{outputdir} . '/' . $fileName
181             or $logger->fatal( "Cannot open $fileType-file $fileName to read" );
182 0           <$fh>;
183             };
184 0           return ( $fileType, encode_base64( $file ) );
185             }
186             else {
187 0           return ( $fileType, $fileName );
188             }
189             }
190              
191              
192             sub noteFromStore {
193 0     0 1   my $self = shift;
194 0           my ( $notectr, %optargs ) = @_;
195            
196 0           my $logger = $BeamerReveal::Log::logger;
197              
198 0           my $fileName = $self->{notes}->[$notectr];
199 0           my $fileType = $self->{mtoracle}->mimeTypeOf( $fileName );
200            
201 0 0         if( exists $optargs{to_embed} ) {
202 0           my $file = do {
203 0           local $/ = undef;
204 0 0         open my $fh, '<' . $self->{outputdir} . '/' . $fileName
205             or $logger->fatal( "Cannot open $fileType-file $fileName to read" );
206 0           <$fh>;
207             };
208 0           return ( $fileType, encode_base64( $file ) );
209             }
210             else {
211 0           return ( $fileType, $fileName );
212             }
213             }
214              
215              
216             sub animationRegisterInStore {
217 0     0 1   my $self = shift;
218 0           my ( $animation ) = @_;
219            
220 0           my $logger = $BeamerReveal::Log::logger;
221 0           $Data::Dumper::Terse = 1;
222 0           $Data::Dumper::Indent = 0;
223 0           my $animid = Digest::SHA::hmac_sha256_hex( Data::Dumper->Dump( [ $animation ] ) );
224 0           my $animdir = "$self->{animations}/$animid";
225 0           my $fullpathid = $animdir . ".mp4";
226             # correct the effective path to reside in the output directory
227 0           $animdir = $self->{outputdir} . '/' . $animdir;
228            
229 0 0         unless ( -r ( $self->{outputdir} . '/' . $fullpathid ) ) {
230 0           File::Path::make_path( $animdir );
231              
232 0           my $templateStore = BeamerReveal::TemplateStore->new();
233 0           my $tTemplate = $templateStore->fetch( 'tex', 'animation.tex' );
234             my $tStamps =
235             {
236             'PREAMBLE' => $self->{preamble},
237             'FRAMERATE' => $animation->{framerate},
238             'DURATION' => $animation->{duration},
239 0           'ANIMATION' => builtin::trim($animation->{tex}),
240             };
241 0           my $fileContent = BeamerReveal::TemplateStore::stampTemplate( $tTemplate, $tStamps );
242            
243 0           my $nofFrames = floor( $animation->{framerate} * $animation->{duration} ) + 1;
244             # one extra frame to cover the final value \progress = 1.
245 0           my $nofCores = MCE::Util::get_ncpu();
246 0 0         if ( $nofCores > 4 ) {
247 0           $nofCores = ceil( $nofCores / 2 );
248             }
249            
250 0           $self->{constructionBackOrders}->{animations}->{$animid} =
251             {
252             animation => $animation,
253             fileContent => $fileContent,
254             nofFrames => $nofFrames,
255             nofCores => $nofCores,
256             };
257             }
258            
259 0           return $fullpathid;
260             }
261              
262              
263             sub animationFromStore {
264 0     0 1   my $self = shift;
265 0           my ( $animation, %optargs ) = @_;
266 0           return $self->_rawFromStore( 'Animations', $animation, %optargs );
267             }
268              
269              
270             sub stillRegisterInStore {
271 0     0 1   my $self = shift;
272 0           my ( $still ) = @_;
273            
274 0           my $logger = $BeamerReveal::Log::logger;
275            
276 0           $Data::Dumper::Terse = 1;
277 0           $Data::Dumper::Indent = 0;
278 0           my $stillid = Digest::SHA::hmac_sha256_hex( Data::Dumper->Dump( [ $still ] ) );
279 0           my $stilldir = "$self->{stills}/$stillid";
280 0           my $fullpathid = $stilldir . ".mp4";
281             # correct the effective path to reside in the output directory
282 0           $stilldir = $self->{outputdir} . '/' . $stilldir;
283            
284 0 0         unless ( -r ( $self->{outputdir} . '/' . $fullpathid ) ) {
285 0           File::Path::make_path( $stilldir );
286              
287 0           my $templateStore = BeamerReveal::TemplateStore->new();
288 0           my $tTemplate = $templateStore->fetch( 'tex', 'still.tex' );
289             my $tStamps =
290             {
291             'PREAMBLE' => $self->{preamble},
292 0           'STILL' => builtin::trim($still->{tex}),
293             };
294 0           my $fileContent = BeamerReveal::TemplateStore::stampTemplate( $tTemplate, $tStamps );
295            
296 0           $self->{constructionBackOrders}->{stills}->{$stillid} =
297             {
298             still => $still,
299             fileContent => $fileContent,
300             };
301             }
302 0           return $fullpathid;
303             }
304              
305              
306              
307             sub stillFromStore {
308 0     0 1   my $self = shift;
309 0           my ( $still, %optargs ) = @_;
310 0           return $self->_rawFromStore( 'Stills', $still, %optargs );
311             }
312              
313              
314             sub processAnimationBackOrders {
315 0     0 0   my $self = shift;
316 0           my ( $animProgressId ) = @_;
317            
318 0           my $logger = $BeamerReveal::Log::logger;
319            
320             ####################
321             # treat animations
322            
323 0           my $totalNofBackOrders = scalar keys %{$self->{constructionBackOrders}->{animations}};
  0            
324            
325 0 0         $logger->progress( $animProgressId, 1, 'reusing cached data', 1 ) unless $totalNofBackOrders;
326            
327 0           my $i = -1;
328 0           while ( my ( $animid, $bo ) = each %{$self->{constructionBackOrders}->{animations}} ) {
  0            
329 0           ++$i;
330 0           my $animation = $bo->{animation};
331 0           my $fileContent = $bo->{fileContent};
332 0           my $nofFrames = $bo->{nofFrames};
333 0           my $nofCores = $bo->{nofCores};
334 0           my $animdir = $self->{outputdir} . '/' . $self->{animations} . '/' . $animid;
335            
336             # I cannot get multithreading/multiprocessing to work reliably on MS-Windows
337 0           my $progress;
338 0           my $sliceSize = $nofFrames;
339 0 0         if ( $^O eq 'MSWin32' ) {
    0          
340 0           $logger->log( 2, "- Preparing media generation of $nofFrames (alas, no parallellization on MS-Windows)" );
341 0           $nofCores = 1;
342 0           $progress = MCE::Shared::Scalar->new( 0 );
343             } elsif ( $nofCores == 1 ) {
344 0           $progress = MCE::Shared::Scalar->new( 0 );
345             } else {
346 0           $sliceSize = ceil( $nofFrames / $nofCores );
347 0           $logger->log( 2, "- Preparing media generation of $nofFrames frames in $nofCores threads at $sliceSize frames per thread" );
348 0           $progress = MCE::Shared->scalar( 0 );
349             }
350            
351             # make planning
352 0           my $frameCounter = 0;
353 0           my $planning = [];
354 0           for ( my $core = 0; $core < $nofCores; ++$core ) {
355             # make plan for core
356 0           my $plan = { nstart => $frameCounter,
357             nstop => min( $frameCounter + $sliceSize, $nofFrames ),
358             nr => $core
359             };
360 0           $plan->{slicestart} = 1;
361 0           $plan->{slicestop} = $plan->{nstop} - $plan->{nstart};
362 0           $plan->{nstop} -= 1;
363            
364             # register plan
365 0           push @$planning, $plan;
366            
367             # prepare for next core
368 0           $frameCounter += $sliceSize;
369             }
370            
371 0           $logger->progress( $animProgressId, 0, "animation @{[$i+1]}/$totalNofBackOrders",
  0            
372             $nofFrames + $nofCores * 3 );
373            
374 0 0         if ( $nofCores == 1 ) {
375             # single-threaded
376 0           for ( my $i = 0; $i < @$planning; ++$i ) {
377 0           _animWork( $planning->[$i], $nofCores, $fileContent, $animdir, $self, $animation,
378             $sliceSize, $progress, $animProgressId );
379             }
380             } else {
381 0           my @hobos;
382             # multi-threaded
383 0           for ( my $i = 0; $i < @$planning; ++$i ) {
384             push @hobos, MCE::Hobo->create
385             (
386             sub {
387 0     0     _animWork( @_ )
388 0           }, ( $planning->[$i], $nofCores, $fileContent, $animdir, $self, $animation,
389             $sliceSize, $progress, $animProgressId )
390             );
391             }
392            
393 0           my $activeworkers = @hobos;
394 0           while ( $activeworkers ) {
395            
396 0           my @joinable = MCE::Hobo->list_joinable();
397 0           foreach my $hobo ( @joinable ) {
398 0           $hobo->join();
399 0 0         if ( my $error = $hobo->error() ) {
400 0           $_->kill() for MCE::Hobo->list();
401 0           MCE::Hobo->wait_all();
402 0           exit(-1);
403             }
404 0           --$activeworkers;
405             }
406            
407 0           $logger->progress( $animProgressId, $progress->get() );
408            
409 0           Time::HiRes::usleep(250);
410             }
411            
412             # $_->join for @hobos;
413 0           $logger->log( 2, "- returning to single-threaded operation" );
414             }
415            
416             # rename all files in order
417 0           my $framecounter = 0;
418 0           for ( my $core = 0; $core < @$planning; ++$core ) {
419 0           my $plan = $planning->[$core];
420 0           my $coreId = sprintf( '%0' . nofdigits( $nofCores ) . 'd', $core );
421 0           for ( my $frameno = $plan->{slicestart}; $frameno <= $plan->{slicestop}; ++$frameno ) {
422 0           my $frameId = sprintf( '%0' . nofdigits( $sliceSize ) . 'd', $frameno );
423 0           my $src = "$animdir/frame-$coreId-$frameId.jpg";
424 0           my $dst = sprintf( "$animdir/frame-%06d.jpg", $framecounter++ );
425 0           File::Copy::move( $src, $dst );
426             }
427             }
428            
429             # run ffmpeg or avconv
430 0           my $cmd = [ $self->{ffmpeg}, '-r', "$animation->{framerate}", '-i', 'frame-%06d.jpg', '-vf', 'crop=iw-mod(iw\,2):ih-mod(ih\,2)', 'animation.mp4' ];
431 0           BeamerReveal::IPC::Run::run( $cmd, 0, 8, $animdir, "Error: ffmpeg run failed" );
432 0           File::Copy::move( "$animdir/animation.mp4",
433             "$animdir/../$animid.mp4" );
434            
435            
436 0 0         File::Path::rmtree( $animdir ) unless( defined $self->{debug} );
437            
438             # all is done
439 0           $logger->progress( $animProgressId, 1, "animation @{[$i+1]}/$totalNofBackOrders", 1 );
  0            
440             }
441             }
442              
443             sub processStillBackOrders {
444 0     0 0   my $self = shift;
445 0           my ( $stillProgressId ) = @_;
446            
447 0           my $logger = $BeamerReveal::Log::logger;
448             ###############
449             # treat stills
450            
451 0           my $totalNofBackOrders = scalar keys %{$self->{constructionBackOrders}->{stills}};
  0            
452            
453 0 0         $logger->progress( $stillProgressId, 1, 'reusing cached data', 1 ) unless $totalNofBackOrders;
454            
455 0           my $i = -1;
456 0           while ( my ( $stillid, $bo ) = each %{$self->{constructionBackOrders}->{stills}} ) {
  0            
457 0           ++$i;
458 0           my $still = $bo->{still};
459 0           my $fileContent = $bo->{fileContent};
460 0           my $stilldir = $self->{outputdir} . '/' . $self->{stills} . '/' . $stillid;
461            
462 0           $logger->progress( $stillProgressId, 0, "still @{[$i+1]}/$totalNofBackOrders", 5 );
  0            
463            
464 0           _stillWork( $fileContent, $stilldir, $self, $still, $stillProgressId );
465            
466             # run ffmpeg or avconv
467 0           my $cmd = [ $self->{ffmpeg}, '-r', '1', '-i', 'frame-1.jpg', '-vf', 'crop=iw-mod(iw\,2):ih-mod(ih\,2)', 'still.mp4' ];
468 0           BeamerReveal::IPC::Run::run( $cmd, 0, 8, $stilldir, "Error: ffmpeg run failed" );
469 0           File::Copy::move( "$stilldir/still.mp4",
470             "$stilldir/../$stillid.mp4" );
471            
472 0 0         File::Path::rmtree( $stilldir ) unless( defined $self->{debug} );
473            
474             # all is done
475 0           $logger->progress( $stillProgressId, 5 );
476             }
477             }
478            
479             # =method imageFromStore()
480            
481             # $path = $mm->imageFromStore( $image )
482            
483             # Fetches the unique ID of the C<$image> and returns that ID (the filename of the oject in the media store). The object is put in back-order such that it can be copied later, using C.
484            
485             # =over 4
486              
487             # =item . C<$image>
488              
489             # the $image file to store in the media store.
490              
491             # =item . C<$path>
492              
493             # the path to the image (in the media store)
494              
495             # =back
496              
497             # =cut
498              
499             sub imageFromStore {
500 0     0 0   my $self = shift;
501 0           my ( $image, %optargs ) = @_;
502 0           return $self->_fromStore( 'Images', $image, %optargs );
503             }
504              
505              
506             sub videoFromStore {
507 0     0 1   my $self = shift;
508 0           my ( $video, %optargs ) = @_;
509 0           return $self->_fromStore( 'Videos', $video, %optargs );
510             }
511              
512              
513             sub iframeFromStore {
514 0     0 1   my $self = shift;
515 0           my ( $iframe, %optargs ) = @_;
516 0           return $self->_fromStore( 'Iframes', $iframe , %optargs);
517             }
518              
519              
520             sub audioFromStore {
521 0     0 1   my $self = shift;
522 0           my ( $audio, %optargs ) = @_;
523 0           return $self->_fromStore( 'Audios', $audio, %optargs );
524             }
525              
526            
527             sub _fromStore {
528 0     0     my $self = shift;
529 0           my ( $fileType, $fileName, %optargs ) = @_;
530              
531 0           my $logger = $BeamerReveal::Log::logger;
532            
533 0           my $mimeType = $self->{mtoracle}->mimeTypeOf( $fileName );
534            
535 0 0         if ( exists $optargs{to_embed} ) {
536 0           my $file = do {
537 0           local $/ = undef;
538 0 0         open my $fh, "<". $fileName
539             or $logger->fatal( "Cannot open $fileType-file $fileName to read" );
540 0           <$fh>;
541             };
542            
543 0           return ( $mimeType, encode_base64( $file ) );
544             } else {
545             # find extension
546 0           $logger->log(0, "### " . $fileName );
547 0           my ( undef, undef, $ext ) = File::Basename::fileparse( $fileName, qr/\.[^.]+$/ );
548            
549             # create store id
550 0           my $id = Data::UUID->new();
551 0           my $fullpathid;
552 0           do {
553 0           my $uuid = $id->create();
554 0           $fullpathid = "$self->{base}/media/$fileType/@{[$id->to_string( $uuid )]}$ext";
  0            
555             } until ( ! -e $fullpathid );
556            
557             # register backorder
558 0           push @{$self->{copyBackOrders}},
559             {
560             type => $fileType,
561             from => $fileName,
562 0           to => $self->{outputdir} . '/' . $fullpathid,
563             };
564              
565 0           return ( $mimeType, $fullpathid );
566             }
567             }
568              
569              
570            
571             sub _rawFromStore {
572 0     0     my $self = shift;
573 0           my ( $fileType, $fileName, %optargs ) = @_;
574              
575 0           my $logger = $BeamerReveal::Log::logger;
576            
577 0           my $mimeType = $self->{mtoracle}->mimeTypeOf( $fileName );
578            
579 0 0         if ( exists $optargs{to_embed} ) {
580 0           my $file = do {
581 0           local $/ = undef;
582 0 0         open my $fh, "<". $fileName
583             or $logger->fatal( "Cannot open $fileType-file $fileName to read" );
584 0           <$fh>;
585             };
586 0           return ( $mimeType, encode_base64( $file ) );
587            
588             } else {
589 0           return ( $mimeType, $fileName );
590             }
591             }
592              
593              
594             sub processCopyBackOrders {
595 0     0 1   my $self = shift;
596 0           my ( $progressId ) = @_;
597            
598 0           my $logger = $BeamerReveal::Log::logger;
599            
600 0           my $totalNofBackOrders = @{$self->{copyBackOrders}};
  0            
601            
602 0           for ( my $i = 0; $i < $totalNofBackOrders; ++$i ) {
603 0           my $bo = $self->{copyBackOrders}->[$i];
604            
605             # verify if source file exists
606 0 0         $logger->fatal( "Error: cannot find media file '$bo->{from}'\n" ) unless( -r $bo->{from} );
607            
608             # report progress and copy
609 0           $logger->progress( $progressId, $i, "file $i/$totalNofBackOrders", $totalNofBackOrders );
610 0           File::Copy::cp( $bo->{from}, $bo->{to} );
611             }
612 0           $logger->progress( $progressId, 1, "file $totalNofBackOrders/$totalNofBackOrders", 1 );
613             }
614              
615              
616              
617             sub _animWork {
618 0     0     my ( $plan, $nofCores, $fileContent, $animdir, $self, $animation, $sliceSize, $progress, $progressId ) = @_;
619            
620 0           my $cmd;
621 0           my $logFile = IO::File->new();
622 0           my $coreId = sprintf( '%0' . nofdigits( $nofCores ) . 'd', $plan->{nr} );
623 0           my $logFileName = "$animdir/animation-$coreId-overall.log";
624 0 0         $logFile->open( ">$logFileName" )
625             or die( "Error: cannot open logfile $logFileName" );
626            
627 0           say $logFile "- Generating TeX file";
628             # generate TeX -file
629             my $perCoreContent = BeamerReveal::TemplateStore::stampTemplate
630             ( $fileContent,
631             {
632             SLICESTART => $plan->{slicestart},
633             SLICESTOP => $plan->{slicestop},
634             NSTART => $plan->{nstart},
635             }
636 0           );
637            
638 0           my $texFileName = "$animdir/animation-$coreId.tex";
639 0           my $texFile = IO::File->new();
640 0 0         $texFile->open( ">$texFileName" )
641             or die( "Error: cannot open animation file '$texFileName' for writing\n" );
642 0           print $texFile $perCoreContent;
643 0           $texFile->close();
644            
645 0           say $logFile "- Running TeX";
646             # run TeX
647             $cmd = [ $self->{compiler},
648 0           "-halt-on-error", "-interaction=nonstopmode", "-output-directory=$animdir", "$texFileName" ];
649 0           my $logFilename = $texFileName;
650 0           $logFilename =~ s/\.tex$/.log/;
651            
652 0           my $logger = $BeamerReveal::Log::logger;
653 0           my $counter = 0;
654             BeamerReveal::IPC::Run::runsmart( $cmd, 1, qr/\[(\d+)\]/,
655             sub {
656 0     0     while ( scalar @_ ) {
657 0           my $a = shift @_;
658 0           while ( $a > $counter ) {
659 0           ++$counter;
660 0           $progress->incr();
661 0 0         if ( $nofCores == 1 ) {
662 0           $logger->progress( $progressId, $progress->get() );
663             }
664             }
665             }
666             },
667 0           $coreId,
668             4,
669             undef, # directory
670             "Error: animation generation failed: check $logFilename"
671             );
672            
673 0           say $logFile "- Cropping PDF file";
674             # run pdfcrop
675 0           $cmd = [ $self->{pdfcrop}, '--hires', '--margins', '-0.5', "animation-$coreId.pdf" ];
676 0           BeamerReveal::IPC::Run::run( $cmd, $coreId, 8, $animdir, "Error: pdfcrop run failed" );
677 0           $progress->incr();
678              
679             # run pdftoppm
680 0           my $xrange = 2 * int( $self->{presentationparameters}->{canvaswidth} * $animation->{width} );
681 0           my $yrange = 2 * int( $self->{presentationparameters}->{canvasheight} * $animation->{height} );
682            
683 0           say $logFile "- Generating jpg files";
684             $cmd = [ $self->{pdftoppm},
685 0           '-scale-to-x', "$xrange",
686             '-scale-to-y', '-1',
687             "animation-$coreId-crop.pdf", "./frame-$coreId", '-jpeg' ];
688 0           BeamerReveal::IPC::Run::run( $cmd, $coreId, 8, $animdir, "Error: pdftoppm run failed" );
689 0           $progress->incr();
690              
691             # correct for too short slicesize in filenames coming from pdftoppm
692 0           say $logFile "- Cleaning up jpg files";
693 0           my $currentDigitCnt = nofdigits( $plan->{slicestop} );
694 0           my $desiredDigitCnt = nofdigits( $sliceSize );
695 0 0         if ( $currentDigitCnt < $desiredDigitCnt ) {
696 0           for ( my $i = 1; $i <= $plan->{slicestop}; ++$i ) {
697 0           my $src = sprintf( "$animdir/frame-$coreId-%0${currentDigitCnt}d.jpg", $i );
698 0           my $dst = sprintf( "$animdir/frame-$coreId-%0${desiredDigitCnt}d.jpg", $i );
699 0           File::Copy::move( $src, $dst );
700             }
701             }
702 0           $logFile->close();
703              
704 0           $progress->incr();
705             }
706              
707              
708             sub _stillWork {
709 0     0     my ( $fileContent, $stilldir, $self, $still, $progressId ) = @_;
710              
711 0           my $logger = $BeamerReveal::Log::logger;
712              
713 0           my $cmd;
714 0           my $logFile = IO::File->new();
715 0           my $logFileName = "$stilldir/still.log";
716 0 0         $logFile->open( ">$logFileName" )
717             or die( "Error: cannot open logfile $logFileName" );
718              
719 0           say $logFile "- Generating TeX file";
720             # generate TeX -file
721             my $content = BeamerReveal::TemplateStore::stampTemplate
722             ( $fileContent,
723             {
724             PROGRESS => $still->{progress},
725             }
726 0           );
727              
728 0           my $texFileName = "$stilldir/still.tex";
729 0           my $texFile = IO::File->new();
730 0 0         $texFile->open( ">$texFileName" )
731             or die( "Error: cannot open still file '$texFileName' for writing\n" );
732 0           print $texFile $content;
733 0           $texFile->close();
734 0           $logger->progress( $progressId, 1 );
735            
736 0           say $logFile "- Running TeX";
737             # run TeX
738             $cmd = [ $self->{compiler},
739 0           "-halt-on-error", "-interaction=nonstopmode", "-output-directory=$stilldir", "$texFileName" ];
740 0           my $logFilename = $texFileName;
741 0           $logFilename =~ s/\.tex$/.log/;
742              
743 0           my $counter = 0;
744             BeamerReveal::IPC::Run::runsmart( $cmd, 1, qr/\[(\d+)\]/,
745       0     sub {},
746 0           1,
747             4,
748             undef, # directory
749             "Error: still generation failed: check $logFilename"
750             );
751 0           $logger->progress( $progressId, 2 );
752            
753 0           say $logFile "- Cropping PDF file";
754             # run pdfcrop
755 0           $cmd = [ $self->{pdfcrop}, '--hires', '--margins', '-0.5', "still.pdf" ];
756 0           BeamerReveal::IPC::Run::run( $cmd, 1, 8, $stilldir, "Error: pdfcrop run failed" );
757 0           $logger->progress( $progressId, 3 );
758              
759             # run pdftoppm
760 0           my $xrange = 2 * int( $self->{presentationparameters}->{canvaswidth} * $still->{width} );
761 0           my $yrange = 2 * int( $self->{presentationparameters}->{canvasheight} * $still->{height} );
762            
763 0           say $logFile "- Generating jpg file";
764             $cmd = [ $self->{pdftoppm},
765 0           '-scale-to-x', "$xrange",
766             '-scale-to-y', '-1',
767             "still-crop.pdf", "./frame", '-jpeg' ];
768 0           BeamerReveal::IPC::Run::run( $cmd, 1, 8, $stilldir, "Error: pdftoppm run failed" );
769 0           $logFile->close();
770 0           $logger->progress( $progressId, 4 );
771             }
772              
773            
774             1;
775              
776             __END__