File Coverage

blib/lib/BeamerReveal/MediaManager.pm
Criterion Covered Total %
statement 66 456 14.4
branch 0 74 0.0
condition n/a
subroutine 22 51 43.1
pod 17 22 77.2
total 105 603 17.4


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