~jstys-z/helioviewer.org/client5

« back to all changes in this revision

Viewing changes to api/src/Module/Movies.php

  • Committer: jeff Stys
  • Date: 2014-10-15 18:56:44 UTC
  • Revision ID: jstys@sesda3.com-20141015185644-mrgwlk83a3t4ai3z
Added two APIs, updated API docs, added database methods to enable movie re-generation, modified data coverage timeline statistics database table, skip missing JP2 files during JPX generation and log missing filenames, updated flowplayer from version 3.2.8 to version 5.4.6, optimized event marker PNG file sizes.

CREATE TABLE `data_coverage_30_min` (
  `date` datetime NOT NULL,
  `sourceId` int(10) unsigned NOT NULL,
  `count` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`date`,`sourceId`),
  KEY `index1` (`date`),
  KEY `index2` (`sourceId`),
  KEY `index3` (`sourceId`,`date`),
  KEY `index4` (`date`,`sourceId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Show diffs side-by-side

added added

removed removed

Lines of Context:
217
217
    }
218
218
 
219
219
    /**
 
220
     * Queues a request for a Helioviewer.org movie
 
221
     */
 
222
    public function reQueueMovie($silent=false) {
 
223
        include_once 'lib/alphaID/alphaID.php';
 
224
        include_once 'lib/Resque.php';
 
225
        include_once 'lib/Redisent/Redisent.php';
 
226
        include_once 'src/Helper/HelioviewerLayers.php';
 
227
        include_once 'src/Helper/HelioviewerEvents.php';
 
228
        include_once 'src/Database/MovieDatabase.php';
 
229
        include_once 'src/Database/ImgIndex.php';
 
230
        include_once 'src/Movie/HelioviewerMovie.php';
 
231
 
 
232
        // Connect to redis
 
233
        $redis = new Redisent('localhost');
 
234
 
 
235
        // If the queue is currently full, don't process the request
 
236
        $queueSize = Resque::size('on_demand_movie');
 
237
        if ( $queueSize >= MOVIE_QUEUE_MAX_SIZE ) {
 
238
            throw new Exception(
 
239
                'Sorry, due to current high demand, we are currently unable ' .
 
240
                'to process your request. Please try again later.', 40);
 
241
        }
 
242
 
 
243
        // Get current number of on_demand_movie workers
 
244
        $workers = Resque::redis()->smembers('workers');
 
245
        $movieWorkers = array_filter($workers, function ($elem) {
 
246
            return strpos($elem, 'on_demand_movie') !== false;
 
247
        });
 
248
 
 
249
        // Default options
 
250
        $defaults = array(
 
251
            "format"      => 'mp4',
 
252
            "force"       => false
 
253
        );
 
254
        $options = array_replace($defaults, $this->_params);
 
255
 
 
256
        // Convert public alpha-numeric id to integer
 
257
        $movieId = alphaId($this->_params['id'], true, 5, HV_MOVIE_ID_PASS);
 
258
        $movieId = intval($movieId);
 
259
 
 
260
        if ( $movieId <= 0 ) {
 
261
            throw new Exception(
 
262
                'Value of movie "id" parameter is invalid.', 25);
 
263
        }
 
264
 
 
265
        // Check if movie exists on disk before re-queueing
 
266
        if ( $options['force'] === false ) {
 
267
            $helioviewerMovie = new Movie_HelioviewerMovie(
 
268
                $this->_params['id'], $options['format']);
 
269
            $filepath = $helioviewerMovie->getFilepath();
 
270
 
 
271
            $path_parts = pathinfo($filepath);
 
272
            $extension = '.' . $path_parts['extension'];
 
273
 
 
274
            foreach ( array('.mp4', '.flv', '.webm') as $ext ) {
 
275
                $path = str_replace($extension, $ext, $filepath);
 
276
                if ( @file_exists($path) ) {
 
277
                    $url = str_replace(HV_CACHE_DIR, HV_CACHE_URL, $path);
 
278
                    throw new Exception(
 
279
                        'Movie file already exists: '.$url, 44);
 
280
                }
 
281
            }
 
282
        }
 
283
 
 
284
        // Get movie metadata from database
 
285
        $movieDatabase = new Database_MovieDatabase();
 
286
        $movie = $movieDatabase->getMovieMetadata($movieId);
 
287
 
 
288
 
 
289
        // Check if movie is already in the queue (status=0)
 
290
        // or is already being processed (status=1) before re-queueing.
 
291
        // This prevents a spider, bot, or other automated user-agent
 
292
        // from stuffing the queue with redundant regeneration requests.
 
293
        // As such, the optional 'force' parameter will NOT override
 
294
        // this check.
 
295
        // However, if the movie status is considered stale, then
 
296
        // a Queued or Processing status is ignored and re-queueing
 
297
        // is allowed to proceed.
 
298
        $movieFormats = $movieDatabase->getMovieFormats($movieId);
 
299
        foreach ( $movieFormats as $movieFormat ) {
 
300
            $seconds_ago = time() - strtotime($movieFormat['modified']);
 
301
            $stale = 60 * 60 * 2;  // 2 hours
 
302
 
 
303
            if ( $movieFormat['status'] == 0
 
304
                && $seconds_ago < $stale ) {
 
305
 
 
306
                return;
 
307
            }
 
308
            else if ( $movieFormat['status'] == 1
 
309
                &&  $seconds_ago < $stale ) {
 
310
 
 
311
                return;
 
312
            }
 
313
        }
 
314
 
 
315
        $numPixels = $movie['width'] * $movie['height'];
 
316
        $maxFrames = min($this->_getMaxFrames($queueSize),
 
317
            $movie['maxFrames']);
 
318
 
 
319
 
 
320
        // Create a connection to the database
 
321
        $db = new Database_ImgIndex();
 
322
 
 
323
        // Limit movies to three layers
 
324
        $layers = new Helper_HelioviewerLayers($movie['dataSourceString']);
 
325
        if ( $layers->length() < 1 || $layers->length() > 3 ) {
 
326
            throw new Exception(
 
327
                'Invalid layer choices! You must specify 1-3 comma-separated '.
 
328
                'layer names.', 22);
 
329
        }
 
330
 
 
331
        // Estimate the number of frames
 
332
        $numFrames = $this->_estimateNumFrames($db, $layers,
 
333
            $movie['startDate'], $movie['endDate']);
 
334
        $numFrames = min($numFrames, $maxFrames);
 
335
 
 
336
        // Estimate the time to create movie frames
 
337
        // @TODO 06/2012: Factor in total number of workers and number of
 
338
        //                workers that are currently available?
 
339
        $estBuildTime = $this->_estimateMovieBuildTime($movieDatabase,
 
340
            $numFrames, $numPixels, $options['format']);
 
341
 
 
342
        // If all workers are in use, increment and use estimated wait counter
 
343
        if ( $queueSize +1 >= sizeOf($movieWorkers) ) {
 
344
            $eta = $redis->incrby('helioviewer:movie_queue_wait',
 
345
                $estBuildTime);
 
346
            $updateCounter = true;
 
347
        }
 
348
        else {
 
349
            // Otherwise simply use the time estimated for the single movie
 
350
            $eta = $estBuildTime;
 
351
            $updateCounter = false;
 
352
        }
 
353
 
 
354
        // Get datasource bitmask
 
355
        $bitmask = bindec($layers->getBitMask());
 
356
 
 
357
        $publicId = $this->_params['id'];
 
358
 
 
359
        // Queue movie request
 
360
        $args = array(
 
361
            'movieId' => $publicId,
 
362
            'eta'     => $estBuildTime,
 
363
            'format'  => $options['format'],
 
364
            'counter' => $updateCounter
 
365
        );
 
366
        $token = Resque::enqueue('on_demand_movie', 'Job_MovieBuilder',
 
367
            $args, true);
 
368
 
 
369
        // Create entries for each version of the movie in the movieFormats
 
370
        // table
 
371
        $movieDatabase->deleteMovieFormats($movieId);
 
372
        foreach(array('mp4', 'webm') as $format) {
 
373
            $movieDatabase->insertMovieFormat($movieId, $format);
 
374
        }
 
375
 
 
376
        // Print response
 
377
        $response = array(
 
378
            'id'    => $publicId,
 
379
            'eta'   => $eta,
 
380
            'queue' => max(0, $queueSize + 1 - sizeOf($movieWorkers)),
 
381
            'token' => $token
 
382
        );
 
383
 
 
384
        if ( !$silent ) {
 
385
            $this->_printJSON(json_encode($response));
 
386
        }
 
387
    }
 
388
 
 
389
    /**
220
390
     * Estimates the amount of time (in seconds) it will take to build the
221
391
     * requested movie using information about the most recent n movies
222
392
     * created.
412
582
        $movie = new Movie_HelioviewerMovie($this->_params['id'],
413
583
                                            $this->_params['format']);
414
584
 
415
 
        // Default options
416
 
        $defaults = array(
417
 
            "hq" => false
418
 
        );
419
 
        $options = array_replace($defaults, $this->_options);
420
 
 
421
 
 
422
 
        // If the movie is finished return the file as an attachment
423
 
        if ( $movie->status == 2 ) {
 
585
        if ( $this->_verifyMediaExists($movie, $allowRegeneration=true) ) {
 
586
            // Default options
 
587
            $defaults = array(
 
588
                "hq" => false
 
589
            );
 
590
            $options = array_replace($defaults, $this->_options);
 
591
 
424
592
            // Get filepath
425
593
            $filepath = $movie->getFilepath($options['hq']);
426
594
            $filename = basename($filepath);
439
607
 
440
608
            // Return movie data
441
609
            echo @file_get_contents($filepath);
442
 
 
443
610
        }
444
 
        // Otherwise return an error
445
611
        else {
446
 
            header('Content-type: application/json');
447
 
            $response = array('error' => 'The movie you requested is either '.
448
 
                'being processed or does not exist.');
449
 
            print json_encode($response);
450
 
        }
 
612
            switch ($movie->status) {
 
613
            case 0:
 
614
                header('Content-type: application/json');
 
615
                $response = array(
 
616
                    'error' => 'Movie '.$movie->publicId.' ('.$movie->id.') '
 
617
                             . 'is queued for processing. '
 
618
                             . 'Please wait for it to complete.');
 
619
                print json_encode($response);
 
620
                break;
 
621
            case 1:
 
622
                header('Content-type: application/json');
 
623
                $response = array(
 
624
                    'error' => 'Movie '.$movie->publicId.' ('.$movie->id.') '
 
625
                             . 'is currently being processed. '
 
626
                             . 'Please wait for it to complete.');
 
627
                print json_encode($response);
 
628
                break;
 
629
            case 3:
 
630
                header('Content-type: application/json');
 
631
                $response = array(
 
632
                    'error' => 'Movie '.$movie->publicId.' ('.$movie->id.') '
 
633
                             . 'was not generated successfully.');
 
634
                print json_encode($response);
 
635
                break;
 
636
            default:
 
637
                header('Content-type: application/json');
 
638
                $response = array(
 
639
                    'error' => 'Movie '.$movie->publicId.' ('.$movie->id.') '
 
640
                             . 'has an unknown status.');
 
641
                print json_encode($response);
 
642
                break;
 
643
            }
 
644
        }
 
645
    }
 
646
 
 
647
    /**
 
648
     * Grab the textual equivalent of a movie status code.
 
649
     *
 
650
     * @return string
 
651
     */
 
652
    public function getStatusLabel($statusCode) {
 
653
        switch ($statusCode) {
 
654
        case 0:
 
655
            $statusLabel = 'Queued';
 
656
            break;
 
657
        case 1:
 
658
            $statusLabel = 'Processing';
 
659
            break;
 
660
        case 2:
 
661
            $statusLabel = 'Completed';
 
662
            break;
 
663
        case 3:
 
664
            $statusLabel = 'Invalid';
 
665
            break;
 
666
        default:
 
667
            $statusLabel = 'Unknown';
 
668
        }
 
669
 
 
670
        return $statusLabel;
451
671
    }
452
672
 
453
673
    /**
458
678
     */
459
679
    public function getMovieStatus() {
460
680
        include_once 'src/Movie/HelioviewerMovie.php';
 
681
        require_once 'lib/Resque.php';
 
682
        $queueNum = $this->_getQueueNum('on_demand_movie',
 
683
            $this->_params['id']) + 1;
461
684
 
462
685
        // Process request
463
686
        $movie = new Movie_HelioviewerMovie($this->_params['id'],
465
688
        $verbose = isset($this->_options['verbose']) ?
466
689
            $this->_options['verbose'] : false;
467
690
 
468
 
        // FINISHED
469
 
        if ($movie->status == 2) {
 
691
 
 
692
        if ($movie->status == 0) {
 
693
            // QUEUED
 
694
            $response = array(
 
695
                'status'   => $movie->status,
 
696
                'statusLabel' => $this->getStatusLabel($movie->status),
 
697
                'queuePosition' => $queueNum,
 
698
                'currentFrame' => 0
 
699
            );
 
700
        }
 
701
        else if ($movie->status == 1) {
 
702
            $current_frame = $movie->getCurrentFrame();
 
703
            $progress = $current_frame / $movie->numFrames;
 
704
            $progress = (float)number_format($progress, 3);
 
705
 
 
706
            $response = array(
 
707
                'status' => $movie->status,
 
708
                'statusLabel' => $this->getStatusLabel($movie->status),
 
709
                'currentFrame' => $current_frame,
 
710
                'numFrames' => $movie->numFrames,
 
711
                'progress' => $progress,
 
712
                'queuePosition' => $queueNum
 
713
            );
 
714
        }
 
715
        else if ($movie->status == 2) {
 
716
            // FINISHED
470
717
            $response = $movie->getCompletedMovieInformation($verbose);
 
718
            $response['statusLabel'] =
 
719
                $this->getStatusLabel($response['status']);
471
720
        }
472
721
        else if ($movie->status == 3) {
473
722
            // ERROR
474
723
            $response = array(
475
 
                'status' => 3,
 
724
                'status' => $movie->status,
 
725
                'statusLabel' => $this->getStatusLabel($movie->status),
476
726
                'error'  => 'Sorry, we are unable to create your movie at '.
477
727
                    'this time. Please try again later.'
478
728
            );
479
729
        }
480
 
        else if ($movie->status == 0) {
481
 
            // QUEUED
482
 
            if ( isset($this->_options['token']) ) {
483
 
                require_once 'lib/Resque.php';
484
 
 
485
 
                // with token
486
 
 
487
 
                // NOTE: since resque token is only useful for determining
488
 
                //       the general status of a job (e.g. QUEUED) and queue
489
 
                //       position can be found using the movie id, the tokenId
490
 
                //       can probably be removed.
491
 
                //$queueNum  = $this->_getQueueNum("on_demand_movie",
492
 
                //    $this->_options['token']);
493
 
                $queueNum  = $this->_getQueueNum('on_demand_movie',
494
 
                    $this->_params['id']);
495
 
                $queueSize = Resque::size('on_demand_movie');
496
 
 
497
 
                $response = array(
498
 
                    'status'   => 0,
499
 
                    'position' => $queueNum,
500
 
                    'total'    => $queueSize
501
 
                );
502
 
            }
503
 
            else {
504
 
                // without token
505
 
                $response = array('status' => 0);
506
 
            }
507
 
        }
508
730
        else {
509
 
            // PROCESSING
510
731
            $response = array(
511
 
                'status' => 1
 
732
                'status' => $movie->status,
 
733
                'statusLabel' => $this->getStatusLabel($movie->status),
 
734
                'queuePosition' => $queueNum
512
735
            );
513
736
        }
514
737
 
692
915
    }
693
916
 
694
917
    /**
 
918
     *
 
919
     *
 
920
     */
 
921
    public function _verifyMediaExists($movie, $allowRegeneration=true) {
 
922
 
 
923
        // Check for missing movie or preview images
 
924
        $media_exists = true;
 
925
        $info = $movie->getCompletedMovieInformation(true);
 
926
        $url_array = $info['thumbnails'];
 
927
        array_push($url_array, $info['url']);
 
928
        foreach ($url_array as $key => $url) {
 
929
            $path = str_replace(HV_CACHE_URL, HV_CACHE_DIR, $url);
 
930
            if ( !@file_exists($path) ) {
 
931
                $media_exists = false;
 
932
            }
 
933
        }
 
934
        if ( !$media_exists && $allowRegeneration ) {
 
935
            try {
 
936
                // Attempt to re-generate the movie because one or more
 
937
                // of the thumbnail images or movie files is missing.
 
938
                // Use the 'force' option to overwrite any of
 
939
                // the associated movie files that may still exist
 
940
                // in the cache
 
941
                $id = $this->_params['id'];
 
942
                $this->_params = array();
 
943
                $this->_params['action'] = 'reQueueMovie';
 
944
                $this->_params['id'] = $id;
 
945
                $this->_params['force'] = true;
 
946
$this->_params['force'] = false;
 
947
                $this->reQueueMovie($silent=true);
 
948
            }
 
949
            catch (Exception $e) {
 
950
                error_log(json_encode($e->getMessage()));
 
951
            }
 
952
        }
 
953
 
 
954
        return $media_exists;
 
955
    }
 
956
 
 
957
    /**
695
958
     * Generates HTML for a video player with the specified movie loaded
696
959
     *
697
960
     * 2011/05/25
710
973
        $movie = new Movie_HelioviewerMovie($this->_params['id'],
711
974
                                            $this->_params['format']);
712
975
 
 
976
        // Check that the movie (in the requested format) as well as
 
977
        // its thumbnail images exist on disk.  If not, silently
 
978
        // queue the movie for re-generation.
 
979
        $this->_verifyMediaExists($movie, $allowRegeneration=true);
 
980
 
713
981
        // Default options
714
982
        $defaults = array(
715
983
            'hq'     => false,
718
986
        );
719
987
        $options = array_replace($defaults, $this->_options);
720
988
 
721
 
        // Return an error if movie is not available
722
 
        if ($movie->status != 2) {
723
 
            header('Content-type: application/json');
724
 
            $response = array(
725
 
                'error' => 'The movie you requested is either being ' .
726
 
                    'processed or does not exist.'
727
 
            );
728
 
 
729
 
            print json_encode($response);
730
 
            return;
731
 
        }
732
 
 
733
989
        $dimensions = sprintf('width: %dpx; height: %dpx;',
734
990
                              $options['width'], $options['height']);
735
991
 
737
993
        $filepath = $movie->getFilepath($options['hq']);
738
994
        $filename = basename($filepath);
739
995
 
 
996
        // Return an error if movie is not available
 
997
        if ( !@file_exists($filepath) ) {
 
998
            header('Content-type: application/json');
 
999
            $response = array(
 
1000
                'error' => 'Movie '.$movie->publicId.' ('.$movie->id.') '
 
1001
                         . 'is not currently available. '
 
1002
                         . 'Please check back later.'
 
1003
            );
 
1004
 
 
1005
            print json_encode($response);
 
1006
            return;
 
1007
        }
 
1008
 
740
1009
        // Movie URL
741
1010
        $url = str_replace(HV_CACHE_DIR, HV_CACHE_URL, $filepath);
742
1011
?>
744
1013
<html>
745
1014
<head>
746
1015
    <title>Helioviewer.org - <?php echo $filename?></title>
747
 
    <script src="<?php echo HV_WEB_ROOT_URL; ?>/lib/flowplayer/flowplayer-3.2.8.min.js"></script>
 
1016
    <!-- player skin -->
 
1017
    <link rel="stylesheet" type="text/css" href="<?php echo HV_WEB_ROOT_URL; ?>/lib/flowplayer-5.4.6/skin/minimalist.css">
 
1018
    <!-- flowplayer depends on jQuery 1.7.1+ (for now) -->
 
1019
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
 
1020
    <!-- include flowplayer -->
 
1021
    <script type="text/javascript" src="<?php echo HV_WEB_ROOT_URL; ?>/lib/flowplayer-5.4.6/flowplayer.min.js"></script>
748
1022
</head>
749
1023
<body>
750
 
    <!-- Movie player -->
751
 
    <div href="<?php echo $url; ?>"
752
 
       style="display:block; <?php print $dimensions; ?>"
753
 
       id="movie-player">
754
 
    </div>
755
 
    <br>
756
 
    <script language="javascript">
757
 
        flowplayer("movie-player", "<?php echo HV_WEB_ROOT_URL; ?>/lib/flowplayer/flowplayer-3.2.8.swf", {
758
 
            clip: {
759
 
                autoBuffering: true,
760
 
                scaling: "fit"
761
 
            }
762
 
        });
763
 
    </script>
 
1024
 
 
1025
 
 
1026
   <!-- the player -->
 
1027
   <div class="flowplayer" data-swf="<?php echo HV_WEB_ROOT_URL; ?>/lib/flowplayer-5.4.6/flowplayer.swf" style="display:block; <?php print $dimensions; ?>;" id="movie-player">
 
1028
      <video>
 
1029
         <source src="<?php echo $url; ?>">
 
1030
      </video>
 
1031
   </div>
764
1032
</body>
765
1033
</html>
766
1034
<?php
855
1123
                'ints'     => array('maxFrames', 'width', 'height')
856
1124
            );
857
1125
            break;
 
1126
        case 'reQueueMovie':
 
1127
            $expected = array(
 
1128
                'required' => array('id'),
 
1129
                'optional' => array('force', 'callback'),
 
1130
                'alphanum' => array('id', 'callback'),
 
1131
                'bools'    => array('force')
 
1132
            );
 
1133
            break;
858
1134
        case 'uploadMovieToYouTube':
859
1135
            $expected = array(
860
1136
                'required' => array('id'),
899
1175
        return true;
900
1176
    }
901
1177
}
902
 
?>
 
 
b'\\ No newline at end of file'
 
1178
?>