~jstys-z/helioviewer.org/client5

« back to all changes in this revision

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

  • Committer: Keith Hughitt
  • Date: 2012-08-10 18:15:41 UTC
  • mfrom: (402.4.61 hv)
  • Revision ID: keith.hughitt@nasa.gov-20120810181541-4zkdf7td1igj55lw
Merged in development changes

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
 
3
/**
 
4
 * Helioviewer Movies Module class definition
 
5
 *
 
6
 * PHP version 5
 
7
 *
 
8
 * @category Configuration
 
9
 * @package  Helioviewer
 
10
 * @author   Keith Hughitt <keith.hughitt@nasa.gov>
 
11
 * @author   Jaclyn Beck <jaclyn.r.beck@gmail.com>
 
12
 * @license  http://www.mozilla.org/MPL/MPL-1.1.html Mozilla Public License 1.1
 
13
 * @link     http://launchpad.net/helioviewer.org
 
14
 */
 
15
require_once 'interface.Module.php';
 
16
 
 
17
/**
 
18
 * Movie generation and display.
 
19
 *
 
20
 * @category Configuration
 
21
 * @package  Helioviewer
 
22
 * @author   Keith Hughitt <keith.hughitt@nasa.gov>
 
23
 * @author   Jaclyn Beck <jaclyn.r.beck@gmail.com>
 
24
 * @license  http://www.mozilla.org/MPL/MPL-1.1.html Mozilla Public License 1.1
 
25
 * @link     http://launchpad.net/helioviewer.org
 
26
 *
 
27
 */
 
28
class Module_Movies implements Module
 
29
{
 
30
    private $_params;
 
31
    private $_options;
 
32
 
 
33
    /**
 
34
     * Movie module constructor
 
35
     *
 
36
     * @param mixed &$params API request parameters
 
37
     */
 
38
    public function __construct(&$params)
 
39
    {
 
40
        $this->_params = $params;
 
41
        $this->_options = array();
 
42
    }
 
43
 
 
44
    /**
 
45
     * execute
 
46
     *
 
47
     * @return void
 
48
     */
 
49
    public function execute()
 
50
    {
 
51
        if ($this->validate()) {
 
52
            try {
 
53
                $this->{$this->_params['action']}();
 
54
            } catch (Exception $e) {
 
55
                handleError($e->getMessage(), $e->getCode());
 
56
            }
 
57
        }
 
58
    }
 
59
    
 
60
    /**
 
61
     * Queues a request for a Helioviewer.org movie
 
62
     */
 
63
    public function queueMovie()
 
64
    {
 
65
        include_once 'lib/alphaID/alphaID.php';
 
66
        include_once 'lib/Resque.php';
 
67
        include_once 'lib/Redisent/Redisent.php';
 
68
        include_once 'src/Helper/HelioviewerLayers.php';
 
69
        include_once 'src/Database/MovieDatabase.php';
 
70
        include_once 'src/Database/ImgIndex.php';
 
71
        
 
72
        // Connect to redis
 
73
        $redis = new Redisent('localhost');
 
74
 
 
75
        // If the queue is currently full, don't process the request
 
76
        $queueSize = Resque::size('on_demand_movie');
 
77
        if ($queueSize >= MOVIE_QUEUE_MAX_SIZE) {
 
78
            throw new Exception("Sorry, due to current high demand, we are currently unable to process your request. " .
 
79
                                "Please try again later.", 40);
 
80
        }
 
81
        
 
82
        // Get current number of on_demand_movie workers
 
83
        $workers = Resque::redis()->smembers("workers");
 
84
        $movieWorkers = array_filter($workers, function ($elem) {
 
85
            return strpos($elem, "on_demand_movie") !== false;
 
86
        });
 
87
                
 
88
        // Default options
 
89
        $defaults = array(
 
90
            "format"      => "mp4",
 
91
            "frameRate"   => NULL,
 
92
            "movieLength" => NULL,
 
93
            "maxFrames"   => HV_MAX_MOVIE_FRAMES,
 
94
            "watermark"   => TRUE
 
95
        );
 
96
        $options = array_replace($defaults, $this->_options);
 
97
        
 
98
        // Default to 15fps if no frame-rate or length was specified
 
99
        if (is_null($options['frameRate']) && is_null($options['movieLength'])) {
 
100
            $options['frameRate'] = 15;
 
101
        }
 
102
        
 
103
        // Limit movies to three layers
 
104
        $layers = new Helper_HelioviewerLayers($this->_params['layers']);
 
105
        if ($layers->length() < 1 || $layers->length() > 3) {
 
106
            throw new Exception("Invalid layer choices! You must specify 1-3 comma-separated layer names.", 22);
 
107
        }
 
108
        
 
109
        // TODO 2012/04/11
 
110
        // Discard any layers which do not share an overlap with the roi to
 
111
        // avoid generating kdu_expand errors later. Check is performed already
 
112
        // on front-end, but should also be done before queuing a request.
 
113
 
 
114
        // Determine the ROI
 
115
        $roi = $this->_getMovieROI($options);
 
116
        $roiString = $roi->getPolygonString();
 
117
        
 
118
        $numPixels = $roi->getPixelWidth() * $roi->getPixelHeight();
 
119
        
 
120
        // Use reduce image scale if necessary
 
121
        $imageScale = $roi->imageScale();
 
122
 
 
123
        // Max number of frames
 
124
        $maxFrames = min($this->_getMaxFrames($queueSize), $options['maxFrames']);
 
125
        
 
126
        // Create a connection to the database
 
127
        $db = new Database_ImgIndex();
 
128
        $movieDb = new Database_MovieDatabase();
 
129
        
 
130
        // Estimate the number of frames
 
131
        $numFrames = $this->_estimateNumFrames($db, $layers, $this->_params['startTime'], $this->_params['endTime']);
 
132
        $numFrames = min($numFrames, $maxFrames);
 
133
        
 
134
        // Estimate the time to create movie frames
 
135
        // @TODO 06/2012: Factor in total number of workers and number of workers
 
136
        // that are currently available?
 
137
        $estBuildTime = $this->_estimateMovieBuildTime($movieDb, $numFrames, $numPixels, $options['format']);
 
138
 
 
139
        // If all workers are in use, increment and use estimated wait counter
 
140
        if($queueSize +1 >= sizeOf($movieWorkers)) {
 
141
            $eta = $redis->incrby('helioviewer:movie_queue_wait', $estBuildTime);
 
142
            $updateCounter = true;
 
143
        } else {
 
144
            // Otherwise simply use the time estimated for the single movie
 
145
            $eta = $estBuildTime;
 
146
            $updateCounter = false;
 
147
        }
 
148
 
 
149
        // Get datasource bitmask
 
150
        $bitmask = bindec($layers->getBitMask());
 
151
        
 
152
        // Create entry in the movies table in MySQL
 
153
        $dbId = $movieDb->insertMovie($this->_params['startTime'], $this->_params['endTime'], $imageScale, 
 
154
                                      $roiString, $maxFrames, $options['watermark'], $this->_params['layers'], $bitmask, 
 
155
                                      $layers->length(), $queueSize, $options['frameRate'], $options['movieLength']);
 
156
 
 
157
        // Convert id
 
158
        $publicId = alphaID($dbId, false, 5, HV_MOVIE_ID_PASS);
 
159
 
 
160
        // Queue movie request
 
161
        $args = array(
 
162
            'movieId' => $publicId,
 
163
            'eta'     => $estBuildTime,
 
164
            'format'  => $options['format'],
 
165
            'counter' => $updateCounter
 
166
        );
 
167
        $token = Resque::enqueue('on_demand_movie', 'Job_MovieBuilder', $args, true);
 
168
        
 
169
        // Create entries for each version of the movie in the movieFormats table
 
170
        foreach(array('mp4', 'webm') as $format) {
 
171
            $movieDb->insertMovieFormat($dbId, $format);
 
172
        }
 
173
 
 
174
        // Print response
 
175
        $response = array(
 
176
            "id"    => $publicId,
 
177
            "eta"   => $eta, 
 
178
            "queue" => max(0, $queueSize + 1 - sizeOf($movieWorkers)),
 
179
            "token" => $token
 
180
        );
 
181
        
 
182
        $this->_printJSON(json_encode($response));
 
183
    }
 
184
 
 
185
    /**
 
186
     * Estimates the amount of time (in seconds) it will take to build the
 
187
     * requested movie using information about the most recent n movies
 
188
     * created.
 
189
     */
 
190
    private function _estimateMovieBuildTime($movieDb, $numFrames, $numPixels, $format)
 
191
    {
 
192
        // Weights for influence of the number of frames/pixels on the
 
193
        // esimated time
 
194
        $w1 = 0.7;
 
195
        $w2 = 0.3;
 
196
 
 
197
        // Get stats for most recent 100 completed movie requests
 
198
        $stats = $movieDb->getMovieStatistics();
 
199
        
 
200
        // If no movie statistics have been collected yet, skip this step
 
201
        if (sizeOf($stats['time']) === 0) {
 
202
            return 30;
 
203
        }
 
204
        
 
205
        // Calculate linear fit for number of frames and pixel area
 
206
        $l1 = $this->_linear_regression($stats['numFrames'], $stats['time']);
 
207
        $l2 = $this->_linear_regression($stats['numPixels'], $stats['time']);
 
208
        
 
209
        // Estimate time to build movie frames
 
210
        $frameEst = ($w1 * ($l1['m'] * $numFrames + $l1['b']) + 
 
211
                     $w2 * ($l2['m'] * $numPixels + $l2['b']));
 
212
                     
 
213
        // Estimate time to encode movie
 
214
        // Since the time required to encode the video is much smaller than the
 
215
        // time to build the frames the parameters of this estimation are 
 
216
        // hard-coded for now to save time (Feb 15, 2012)
 
217
        
 
218
        // MP4, WebM
 
219
        $encodingEst = max(1, 0.066 * $numFrames + 0.778) +
 
220
                       max(1, 0.094 * $numFrames + 2.298);
 
221
                       
 
222
        // Scale by pixel area
 
223
        $encodingEst = ($numPixels / (1920 * 1200)) * $encodingEst;
 
224
 
 
225
        return (int) max(10, ($frameEst + $encodingEst));
 
226
    }
 
227
    
 
228
    /**
 
229
     * Linear regression function
 
230
     * 
 
231
     * @param $x array x-coords
 
232
     * @param $y array y-coords
 
233
     * 
 
234
     * @returns array() m=>slope, b=>intercept
 
235
     * 
 
236
     * http://richardathome.wordpress.com/2006/01/25/a-php-linear-regression-function/
 
237
     */
 
238
    private function _linear_regression($x, $y) {
 
239
        $n = count($x);
 
240
        
 
241
        // calculate sums
 
242
        $x_sum = array_sum($x);
 
243
        $y_sum = array_sum($y);
 
244
        
 
245
        $xx_sum = 0;
 
246
        $xy_sum = 0;
 
247
        
 
248
        for($i = 0; $i < $n; $i++) {
 
249
            $xy_sum += ($x[$i] * $y[$i]);
 
250
            $xx_sum += ($x[$i] * $x[$i]);
 
251
        }
 
252
        
 
253
        // Calculate slope
 
254
        $divisor = (($n * $xx_sum) - ($x_sum * $x_sum));
 
255
    
 
256
        if ($divisor == 0) {
 
257
            $m = 0;
 
258
        } else {
 
259
            $m = (($n * $xy_sum) - ($x_sum * $y_sum)) / $divisor;          
 
260
        }
 
261
            
 
262
        // Calculate intercept
 
263
        $b = ($y_sum - ($m * $x_sum)) / $n;
 
264
        
 
265
        // Return result
 
266
        return array("m"=>$m, "b"=>$b);
 
267
    }
 
268
 
 
269
    /**
 
270
     * Determines the maximum number of frames allowed based on the current queue size
 
271
     */
 
272
    private function _getMaxFrames($queueSize)
 
273
    {
 
274
        // Limit max frames if the number of queued movies exceeds one of the specified throttles.
 
275
        if ($queueSize >= MOVIE_QUEUE_THROTTLE_TWO) {
 
276
            return HV_MAX_MOVIE_FRAMES / 2;
 
277
        } elseif ($queueSize >= MOVIE_QUEUE_THROTTLE_ONE) {
 
278
            return (HV_MAX_MOVIE_FRAMES * 3) / 4;
 
279
        }
 
280
        return HV_MAX_MOVIE_FRAMES;        
 
281
    }
 
282
    
 
283
    /**
 
284
     * Returns the region of interest for the movie request or throws an error if one was not properly specified.
 
285
     */
 
286
    private function _getMovieROI($options) {
 
287
        include_once 'src/Helper/RegionOfInterest.php';
 
288
 
 
289
        // Region of interest (center in arcseconds and dimensions in pixels)
 
290
        if (isset($options['x1']) && isset($options['y1']) && isset($options['x2']) && isset($options['y2'])) {
 
291
            $x1 = $options['x1'];
 
292
            $y1 = $options['y1'];
 
293
            $x2 = $options['x2'];
 
294
            $y2 = $options['y2'];
 
295
        } elseif (isset($options['x0']) and isset($options['y0']) and isset($options['width']) and isset($options['height'])) {
 
296
            // Region of interest (top-left and bottom-right coords in arcseconds)
 
297
            $x1 = $options['x0'] - 0.5 * $options['width'] * $this->_params['imageScale'];
 
298
            $y1 = $options['y0'] - 0.5 * $options['height'] * $this->_params['imageScale'];
 
299
            $x2 = $options['x0'] + 0.5 * $options['width'] * $this->_params['imageScale'];
 
300
            $y2 = $options['y0'] + 0.5 * $options['height'] * $this->_params['imageScale'];
 
301
        } else {
 
302
            throw new Exception("Region of interest not properly specified.", 23);
 
303
        }
 
304
 
 
305
        $roi = new Helper_RegionOfInterest($x1, $y1, $x2, $y2, $this->_params['imageScale']);
 
306
 
 
307
        return $roi;
 
308
    }
 
309
    
 
310
    /**
 
311
     * Estimates the number of frames that a movie will include
 
312
     * 
 
313
     * Determines how many frames will be included in the movie and then uses that along with some other
 
314
     * information about the nature of the request to come up with an estimated time it will take to build
 
315
     * the requested movie.
 
316
     * 
 
317
     * NOTE: This is only a temporary work-around. An ideal solution will make use of prior actual movie generation 
 
318
     * times and will not require use of manually-selected system-dependent coefficients
 
319
     */
 
320
    private function _estimateNumFrames($db, $layers, $startTime, $endTime)
 
321
    {
 
322
        $numFrames = 0;
 
323
        $sql =  "SELECT COUNT(*) FROM images WHERE DATE BETWEEN '%s' AND '%s' AND sourceId=%d;";
 
324
        
 
325
        // Estimate number of movies frames for each layer
 
326
        foreach($layers->toArray() as $layer) {
 
327
            $numFrames += $db->getImageCount($startTime, $endTime, $layer['sourceId']);
 
328
        }
 
329
 
 
330
        // Raise an error if few or no frames were found for the request range and data sources
 
331
        if ($numFrames == 0) {
 
332
            throw new Exception("No images found for requested time range. Please try a different time.", 12);
 
333
        } else if ($numFrames <= 3) {
 
334
            throw new Exception("Insufficient data was found for the requested time range. Please try a different time.", 16);
 
335
        }
 
336
        return $numFrames;
 
337
    }
 
338
 
 
339
    /**
 
340
     * Checks to see if the requested movie is available and if so returns
 
341
     * it as a file-attachment
 
342
     * 
 
343
     * @return file Requested movie
 
344
     */
 
345
    public function downloadMovie ()
 
346
    {
 
347
        include_once 'src/Movie/HelioviewerMovie.php';
 
348
        
 
349
        // Load movie
 
350
        $movie = new Movie_HelioviewerMovie($this->_params['id'], 
 
351
                                            $this->_params['format']);
 
352
                                  
 
353
        // Default options
 
354
        $defaults = array(
 
355
            "hq" => false
 
356
        );
 
357
        $options = array_replace($defaults, $this->_options);
 
358
        
 
359
        
 
360
        // If the movie is finished return the file as an attachment
 
361
        if ($movie->status == 2) {
 
362
            // Get filepath
 
363
            $filepath = $movie->getFilepath($options['hq']);
 
364
            $filename = basename($filepath);
 
365
            
 
366
            // Set HTTP headers
 
367
            header("Pragma: public");
 
368
            header("Expires: 0");
 
369
            header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
 
370
            header("Cache-Control: private", false);
 
371
            header("Content-Disposition: attachment; filename=\"" . $filename . "\"");
 
372
            header("Content-Transfer-Encoding: binary");
 
373
            header("Content-Length: " . filesize($filepath));
 
374
            header("Content-type: video/" . $this->_params['format']);
 
375
            
 
376
            // Return movie data
 
377
            echo file_get_contents($filepath);
 
378
            
 
379
        // Otherwise return an error
 
380
        } else {
 
381
            header('Content-type: application/json');
 
382
            $response = array(
 
383
                "error" => "The movie you requested is either being processed
 
384
                            or does not exist."
 
385
            );
 
386
            print json_encode($response);
 
387
        }
 
388
    }
 
389
    
 
390
    /**
 
391
     * Checks to see if a movie is available and returns either a link to the movie if it is ready or progress
 
392
     * information otherwise
 
393
     * 
 
394
     * @return void
 
395
     */
 
396
    public function getMovieStatus ()
 
397
    {
 
398
        include_once 'src/Movie/HelioviewerMovie.php';
 
399
        
 
400
        // Process request
 
401
        $movie = new Movie_HelioviewerMovie($this->_params['id'], $this->_params['format']);
 
402
        $verbose = isset($this->_options['verbose']) ? $this->_options['verbose'] : false;
 
403
        
 
404
        // FINISHED
 
405
        if ($movie->status == 2) {            
 
406
            $response = $movie->getCompletedMovieInformation($verbose);
 
407
        } else if ($movie->status == 3) {
 
408
            // ERROR
 
409
            $response = array(
 
410
                "status" => 3,
 
411
                "error"  => "Sorry, we are unable to create your movie at this time. Please try again later."
 
412
            );
 
413
        } else if ($movie->status == 0) {
 
414
            // QUEUED
 
415
            if (isset($this->_options['token'])) {
 
416
                require_once 'lib/Resque.php';
 
417
                
 
418
                // with token
 
419
                
 
420
                // NOTE: since resque token is only useful for determining the general
 
421
                // status of a job (e.g. QUEUED) and queue position can be found
 
422
                // using the movie id, the tokenId can probably be removed.
 
423
                //$queueNum  = $this->_getQueueNum("on_demand_movie", $this->_options['token']);
 
424
                $queueNum  = $this->_getQueueNum("on_demand_movie", $this->_params['id']);
 
425
                $queueSize = Resque::size('on_demand_movie');
 
426
 
 
427
                $response = array(
 
428
                    "status" => 0,
 
429
                    "position" => $queueNum,
 
430
                    "total" => $queueSize
 
431
                );
 
432
            } else {
 
433
                // without token
 
434
                $response = array("status" => 0);
 
435
            }
 
436
        } else {
 
437
            // PROCESSING
 
438
            $response = array(
 
439
                "status" => 1
 
440
            );
 
441
        }
 
442
        
 
443
        $this->_printJSON(json_encode($response));
 
444
    }
 
445
 
 
446
    /**
 
447
     * Determines the position of a job within a specified queue
 
448
     * 
 
449
     * Note: https://github.com/chrisboulton/php-resque/issues/45
 
450
     * 
 
451
     * @return int Returns queue position or else -1 if job not found
 
452
     */
 
453
    private function _getQueueNum($queue, $id) {
 
454
        $i = 0;
 
455
 
 
456
        foreach (Resque::redis()->lrange("queue:$queue", 0, -1) as $job) {
 
457
            if (strpos($job, $id) !== false) {
 
458
                return $i; 
 
459
            }
 
460
            $i += 1;
 
461
        }
 
462
        
 
463
        return -1;
 
464
    }
 
465
    
 
466
    /**
 
467
     * Checks to see if Helioviewer.org is authorized to upload videos for a user
 
468
     */
 
469
    public function checkYouTubeAuth ()
 
470
    {
 
471
        include_once 'src/Movie/YouTube.php';
 
472
        
 
473
        $youtube = new Movie_YouTube();
 
474
        
 
475
        $this->_printJSON(json_encode($youtube->checkYouTubeAuth()));
 
476
    }
 
477
 
 
478
    /**
 
479
     * Requests authorization for Helioviewer.org to upload videos on behalf
 
480
     * of the user.
 
481
     */
 
482
    public function getYouTubeAuth()
 
483
    {
 
484
        include_once 'src/Movie/YouTube.php';
 
485
        
 
486
        $share = isset($this->_options['share']) ? $this->_options['share'] : false;
 
487
        
 
488
        session_start();
 
489
 
 
490
        // Discard any existing authorization
 
491
        unset($_SESSION['sessionToken']);
 
492
 
 
493
        // Store form data for later use        
 
494
        $_SESSION['video-id'] = $this->_params["id"];
 
495
        $_SESSION['video-title'] = $this->_params["title"];
 
496
        $_SESSION['video-description'] = $this->_params["description"];
 
497
        $_SESSION['video-tags'] = $this->_params["tags"];
 
498
        $_SESSION['video-share'] = $share;
 
499
        
 
500
        $youtube = new Movie_YouTube();
 
501
        $youtube->getYouTubeAuth($this->_params['id']);
 
502
    }
 
503
    
 
504
    /**
 
505
     * Uploads a user-created video to YouTube
 
506
     * 
 
507
     * TODO 2011/05/09: Make sure movie hasn't already been uploaded
 
508
     */
 
509
    public function uploadMovieToYouTube ()
 
510
    {
 
511
        include_once 'src/Movie/HelioviewerMovie.php';
 
512
        include_once 'src/Movie/YouTube.php';
 
513
        
 
514
        // Process request
 
515
        $movie = new Movie_HelioviewerMovie($this->_params['id'], "mp4");
 
516
        
 
517
        if ($movie->status !== 2) {
 
518
            throw new Exception("Invalid movie requested", 41);
 
519
        }
 
520
        
 
521
        // If this was not the first upload for the current session, then
 
522
        // the form data will have been passed in as GET variables
 
523
        if (isset($this->_options["title"])) {
 
524
            $id          = $this->_params["id"];
 
525
            $title       = $this->_options["title"];
 
526
            $description = $this->_options["description"];
 
527
            $tags        = $this->_options["tags"];
 
528
            $share       = isset($this->_options['share']) ? $this->_options['share'] : false;
 
529
        } else {
 
530
            // Otherwise read form data back in from session variables
 
531
            session_start();
 
532
 
 
533
            $id          = $_SESSION['video-id'];
 
534
            $title       = $_SESSION['video-title'];
 
535
            $description = $_SESSION['video-description'];
 
536
            $tags        = $_SESSION['video-tags'];
 
537
            $share       = $_SESSION['video-share'];
 
538
            
 
539
            if (!isset($_SESSION['video-title'])) {
 
540
                $msg = "Error encountered during authentication. ". 
 
541
                       "<a href='https://accounts.google.com/IssuedAuthSubTokens'>Revoke a</a> " . 
 
542
                       "for Helioviewer.org in your Google settings page and try again.</a>";
 
543
                throw new Exception($msg, 42);
 
544
            }
 
545
        }
 
546
        
 
547
        // Output format
 
548
        if (isset($this->_options['html']) && $this->_options['html']) {
 
549
            $html = true;
 
550
        } else {
 
551
            $html = false;
 
552
        }
 
553
        
 
554
        $youtube = new Movie_YouTube();
 
555
        $video = $youtube->uploadVideo($movie, $id, $title, $description, $tags, $share, $html);
 
556
    }
 
557
    
 
558
    /**
 
559
     * Retrieves recent user-submitted videos from YouTube and returns the
 
560
     * result as a JSON array.
 
561
     */
 
562
    public function getUserVideos()
 
563
    {
 
564
        include_once 'src/Database/MovieDatabase.php';
 
565
        include_once 'src/Movie/HelioviewerMovie.php';
 
566
        include_once 'lib/alphaID/alphaID.php';
 
567
        
 
568
        $movies = new Database_MovieDatabase();
 
569
 
 
570
        // Default options
 
571
        $defaults = array(
 
572
            "num" => 10,
 
573
            "since" => '1000/01/01T00:00:00.000Z'
 
574
        );
 
575
        $opts = array_replace($defaults, $this->_options);
 
576
                
 
577
        // Get a list of recent videos
 
578
        $videos = array();
 
579
        
 
580
        foreach($movies->getSharedVideos($opts['num'], $opts['since']) as $video) {
 
581
            $youtubeId = $video['youtubeId'];
 
582
            $movieId   = (int) $video['movieId'];
 
583
             
 
584
            // Convert id
 
585
            $publicId = alphaID($movieId, false, 5, HV_MOVIE_ID_PASS);
 
586
            
 
587
            // Load movie
 
588
            $movie = new Movie_HelioviewerMovie($publicId);
 
589
            
 
590
            // Check to make sure video was not removed by the user
 
591
            // 2011/06/08: Disabling for now since this delays time before videos
 
592
            // $handle = curl_init("http://gdata.youtube.com/feeds/api/videos/$youtubeId?v=2");
 
593
            // curl_setopt($handle, CURLOPT_RETURNTRANSFER, TRUE);
 
594
 
 
595
            // $response = curl_exec($handle);
 
596
            // $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
 
597
 
 
598
            //curl_close($handle);
 
599
 
 
600
            // Only add videos with response code 200
 
601
            //if ($httpCode == 200) {
 
602
            array_push($videos, array(
 
603
                "id"  => $publicId,
 
604
                "url" => "http://www.youtube.com/watch?v=$youtubeId",
 
605
                "thumbnails" => $movie->getPreviewImages(),
 
606
                "published"  => $video['timestamp']
 
607
            ));
 
608
            //}
 
609
        }
 
610
 
 
611
        // HTML
 
612
        /**if ($options['html']) {
 
613
            foreach ($videos as $vid) {
 
614
                printf("<a href='%s'><img src='%s' /><h3>%s</h3></a>", $vid["url"], $vid["thumbnails"]["small"], "test");
 
615
            }
 
616
        } else {**/
 
617
        $this->_printJSON(json_encode($videos));
 
618
    }
 
619
 
 
620
    /**
 
621
     * Generates HTML for a video player with the specified movie loaded
 
622
     *
 
623
     * 2011/05/25
 
624
     * Using direct links to movies to enable support for byte range requests
 
625
     * and provide a better movie experience in Chromium.
 
626
     *  
 
627
     * See: https://groups.google.com/a/webmproject.org/group/webm-discuss
 
628
     * /browse_thread/thread/061f3ffa200d26a9/1ce5f06c76c9cb2d#1ce5f06c76c9cb2d
 
629
     *
 
630
     * @return void
 
631
     */
 
632
    public function playMovie ()
 
633
    {
 
634
        include_once 'src/Movie/HelioviewerMovie.php';
 
635
        
 
636
        // Load movie
 
637
        $movie = new Movie_HelioviewerMovie($this->_params['id'], 
 
638
                                            $this->_params['format']);
 
639
                                  
 
640
        // Default options
 
641
        $defaults = array(
 
642
            "hq"     => false,
 
643
            "width"  => $movie->width,
 
644
            "height" => $movie->height
 
645
        );
 
646
        $options = array_replace($defaults, $this->_options);
 
647
 
 
648
        // Return an error if movie is not available
 
649
        if ($movie->status != 2) {
 
650
            header('Content-type: application/json');
 
651
            $response = array(
 
652
                "error" => "The movie you requested is either being processed
 
653
                            or does not exist."
 
654
            );
 
655
            print json_encode($response);
 
656
            return;
 
657
        }
 
658
 
 
659
        $dimensions = sprintf("width: %dpx; height: %dpx;",
 
660
            $options['width'], $options['height']);
 
661
 
 
662
        // Get filepath
 
663
        $filepath = $movie->getFilepath($options['hq']);
 
664
        $filename = basename($filepath);  
 
665
        
 
666
        // Movie URL
 
667
        $url = str_replace(HV_ROOT_DIR, HV_WEB_ROOT_URL, $filepath);
 
668
        ?>
 
669
<!DOCTYPE html> 
 
670
<html> 
 
671
<head> 
 
672
    <title>Helioviewer.org - <?php echo $filename?></title>
 
673
    <script src="../lib/flowplayer/flowplayer-3.2.8.min.js"></script>            
 
674
</head> 
 
675
<body>
 
676
    <!-- Movie player -->
 
677
    <div href="<?php echo $url;?>" 
 
678
       style="display:block; <?php print $dimensions;?>"
 
679
       id="movie-player">
 
680
    </div>
 
681
    <br>
 
682
    <script language="javascript">
 
683
        flowplayer("movie-player", "../lib/flowplayer/flowplayer-3.2.8.swf", {
 
684
            clip: {
 
685
                autoBuffering: true,
 
686
                scaling: "fit"
 
687
            }
 
688
        });
 
689
    </script>
 
690
</body> 
 
691
</html> 
 
692
        <?php
 
693
    }
 
694
 
 
695
    /**
 
696
     * Helper function to output result as either JSON or JSONP
 
697
     * 
 
698
     * @param string $json JSON object string
 
699
     * @param bool   $xml  Whether to wrap an XML response as JSONP
 
700
     * @param bool   $utf  Whether to return result as UTF-8
 
701
     * 
 
702
     * @return void
 
703
     */
 
704
    private function _printJSON($json, $xml=false, $utf=false)
 
705
    {
 
706
        // Wrap JSONP requests with callback
 
707
        if(isset($this->_params['callback'])) {
 
708
            // For XML responses, surround with quotes and remove newlines to
 
709
            // make a valid JavaScript string
 
710
            if ($xml) {
 
711
                $xmlStr = str_replace("\n", "", str_replace("'", "\'", $json));
 
712
                $json = sprintf("%s('%s')", $this->_params['callback'], $xmlStr);
 
713
            } else {
 
714
                $json = sprintf("%s(%s)", $this->_params['callback'], $json);    
 
715
            }
 
716
        }
 
717
        
 
718
        // Set Content-type HTTP header
 
719
        if ($utf) {
 
720
            header('Content-type: application/json;charset=UTF-8');
 
721
        } else {
 
722
            header('Content-Type: application/json');            
 
723
        }
 
724
        
 
725
        // Print result
 
726
        echo $json;
 
727
    }
 
728
    
 
729
    /**
 
730
     * validate
 
731
     *
 
732
     * @return bool Returns true if input parameters are valid
 
733
     */
 
734
    public function validate()
 
735
    {
 
736
        switch($this->_params['action'])
 
737
        {
 
738
        case "downloadMovie":
 
739
            $expected = array(
 
740
                "required" => array('id', 'format'),
 
741
                "optional" => array('hq'),
 
742
                "alphanum" => array('id', 'format'),
 
743
                "bools"    => array('hq')
 
744
            );
 
745
            break;
 
746
        case "getMovieStatus":
 
747
            $expected = array(
 
748
                "required" => array('id', 'format'),
 
749
                "optional" => array('verbose', 'callback', 'token'),
 
750
                "alphanum" => array('id', 'format', 'callback', 'token'),
 
751
                "bools"    => array('verbose')
 
752
                
 
753
            );
 
754
            break;
 
755
        case "playMovie":
 
756
            $expected = array(
 
757
                "required" => array('id', 'format'),
 
758
                "optional" => array('hq', 'width', 'height'),
 
759
                "alphanum" => array('id', 'format'),
 
760
                "bools"    => array('hq'),
 
761
                "ints"     => array('width', 'height')
 
762
            );
 
763
            break;
 
764
        case "queueMovie":
 
765
            $expected = array(
 
766
                "required" => array('startTime', 'endTime', 'layers', 'imageScale'),
 
767
                "optional" => array('format', 'frameRate', 'maxFrames', 'movieLength', 'watermark', 'width', 'height', 'x0', 'y0', 'x1', 'x2', 'y1', 'y2', 'callback'),
 
768
                "alphanum" => array('format', 'callback'),
 
769
                "bools"    => array('watermark'),
 
770
                "dates"    => array('startTime', 'endTime'),
 
771
                "floats"   => array('imageScale', 'frameRate', 'movieLength', 'x0', 'y0', 'x1', 'x2', 'y1', 'y2'),
 
772
                "ints"     => array('maxFrames', 'width', 'height')
 
773
            );
 
774
            break;
 
775
        case "uploadMovieToYouTube":
 
776
            $expected = array(
 
777
                "required" => array('id'),
 
778
                "optional" => array('title', 'description', 'tags', 'share', 'token', 'html'),
 
779
                "alphanum" => array('id'),
 
780
                "bools"    => array('share', 'html')
 
781
            );
 
782
            break;
 
783
        case "getUserVideos":
 
784
            $expected = array(
 
785
                "optional" => array('num', 'since', 'callback'),
 
786
                "alphanum" => array('callback'),
 
787
                "ints"     => array('num'),
 
788
                "dates"    => array('since')
 
789
            );
 
790
            break;
 
791
        case "checkYouTubeAuth":
 
792
            $expected = array(
 
793
                "optional" => array('callback'),
 
794
                "alphanum" => array('callback')
 
795
            );
 
796
            break;
 
797
        case "getYouTubeAuth":
 
798
            $expected = array(
 
799
                "required" => array('id', 'title', 'description', 'tags'),
 
800
                "optional" => array('share'),
 
801
                "alphanum" => array('id'),
 
802
                "bools"    => array('share')
 
803
            );
 
804
        default:
 
805
            break;
 
806
        }
 
807
 
 
808
        // Check input
 
809
        if (isset($expected)) {
 
810
            Validation_InputValidator::checkInput($expected, $this->_params, $this->_options);
 
811
        }
 
812
 
 
813
        return true;
 
814
    }
 
815
    
 
816
    /**
 
817
     * Prints the Movies module's documentation header
 
818
     * 
 
819
     * @return void
 
820
     */
 
821
    public static function printDocHeader()
 
822
    {
 
823
        ?>
 
824
            <li>
 
825
                <a href="index.php#MovieAPI">Movies</a>
 
826
                <ul>
 
827
                    <li><a href="index.php#queueMovie">Creating a Movie</a></li>
 
828
                </ul>
 
829
                <ul>
 
830
                    <li><a href="index.php#queueMovie">Check a Movie's Status</a></li>
 
831
                </ul>
 
832
                <ul>
 
833
                    <li><a href="index.php#queueMovie">Retrieving a Movie</a></li>
 
834
                </ul>
 
835
            </li>
 
836
        <?php
 
837
    }
 
838
    
 
839
    /**
 
840
     * printDoc
 
841
     *
 
842
     * @return void
 
843
     */
 
844
    public static function printDoc()
 
845
    {
 
846
        ?>
 
847
        <!-- Movie API -->
 
848
        <div id="MovieAPI">
 
849
            <h1>Movies:</h1>
 
850
            <p>The movie API allows users to create time-lapse videos of what they are viewing on the website. There are two steps involved in Helioviewer.org movie requests: 
 
851
               1) queueing the movie, and 2) retrieving the movie once it has been processed.</p>
 
852
            <ol style="list-style-type: upper-latin;">
 
853
                <!-- Movie -->
 
854
                <li>
 
855
                <div id="queueMovie">Queueing a Movie 
 
856
                <p>Because of the high-demands of creating movies on the fly, requests for movies from Helioviewer.org must be added to a queue before being processed. Only when
 
857
                    all of the movies ahead of the requested one have been processed will the request be handled and the movie generated.</p>
 
858
                    
 
859
                <p>Upon queueing a movie, some basic information about the movie request will be returned including an identifier to reference that movie in the future and an estimated
 
860
                    time until the movie has been processsed. This information can be used to check on the movie status using the getMovieStatus API call, and then eventually to either
 
861
                    download or play the movie once it is ready.</p>
 
862
                    
 
863
                <p>Movies may contain between 10 and 300 frames. The movie frames are chosen by matching the closest image available at each step within the specified range 
 
864
                   of dates, and are automatically generated using the Screenshot API calls. The region to be included in the movie may be specified using either the top-left 
 
865
                   and bottom-right coordinates in arc-seconds, or a center point in arc-seconds and a width and height in pixels. See the 
 
866
                   <a style="color:#3366FF" href="#Coordinates">Coordinates Appendix</a> for more infomration about working with coordinates in Helioviewer.org.</p>
 
867
        
 
868
                <br />
 
869
        
 
870
                <div class="summary-box"><span
 
871
                    style="text-decoration: underline;">Usage:</span><br />
 
872
                <br />
 
873
        
 
874
                <?php echo HV_API_ROOT_URL;?>?action=queueMovie<br />
 
875
                <br />
 
876
        
 
877
                Supported Parameters:<br />
 
878
                <br />
 
879
        
 
880
                <table class="param-list" cellspacing="10">
 
881
                    <tbody valign="top">
 
882
                        <tr>
 
883
                            <td width="20%"><b>startTime</b></td>
 
884
                            <td width="20%"><i>ISO 8601 UTC Date</i></td>
 
885
                            <td>Desired starting timestamp of the movie. The timestamps for the subsequent frames are incremented by
 
886
                                a certain timestep.</td>
 
887
                        </tr>
 
888
                        <tr>
 
889
                            <td><b>endTime</b></td>
 
890
                            <td><i>ISO 8601 UTC Date</i></td>
 
891
                            <td>Desired ending timestamp of the movie. Time step and number of frames will be figured out from the range
 
892
                                between startTime and endTime.</td>
 
893
                        </tr>
 
894
                        <tr>
 
895
                            <td><b>imageScale</b></td>
 
896
                            <td><i>Float</i></td>
 
897
                            <td>The zoom scale of the images. Default scales that can be used are 0.6, 1.2, 2.4, and so on, increasing or decreasing by 
 
898
                                a factor of 2. The full-res scale of an AIA image is 0.6.</td>
 
899
                        </tr>                
 
900
                        <tr>
 
901
                            <td><b>layers</b></td>
 
902
                            <td><i>String</i></td>
 
903
                            <td>A string of layer information in the following format:<br />
 
904
                                Each layer is comma-separated with these values: [<i>sourceId,visible,opacity</i>]. <br />
 
905
                                If you do not know the sourceId, you can 
 
906
                                alternately send this layer string: [<i>obs,inst,det,meas,opacity]</i>.
 
907
                                Layer strings are separated by commas: [layer1],[layer2],[layer3].</td>
 
908
                        </tr>
 
909
                        <tr>
 
910
                            <td><b>y1</b></td>
 
911
                            <td><i>Float</i></td>
 
912
                            <td><i>[Optional]</i> The offset of the image's top boundary from the center of the sun, in arcseconds.</td>
 
913
                        </tr>
 
914
                        <tr>
 
915
                            <td><b>x1</b></td>
 
916
                            <td><i>Float</i></td>
 
917
                            <td><i>[Optional]</i> The offset of the image's left boundary from the center of the sun, in arcseconds.</td>
 
918
                        </tr>
 
919
                        <tr>
 
920
                            <td><b>y2</b></td>
 
921
                            <td><i>Float</i></td>
 
922
                            <td><i>[Optional]</i> The offset of the image's bottom boundary from the center of the sun, in arcseconds.</td>
 
923
                        </tr>
 
924
                        <tr>
 
925
                            <td><b>x2</b></td>
 
926
                            <td><i>Float</i></td>
 
927
                            <td><i>[Optional]</i> The offset of the image's right boundary from the center of the sun, in arcseconds.</td>
 
928
                        </tr>
 
929
                        <tr>
 
930
                            <td><b>x0</b></td>
 
931
                            <td><i>Float</i></td>
 
932
                            <td><i>[Optional]</i> The horizontal offset from the center of the Sun.</td>
 
933
                        </tr>
 
934
                        <tr>
 
935
                            <td><b>y0</b></td>
 
936
                            <td><i>Float</i></td>
 
937
                            <td><i>[Optional]</i> The vertical offset from the center of the Sun.</td>
 
938
                        </tr>
 
939
                        <tr>
 
940
                            <td><b>width</b></td>
 
941
                            <td><i>Integer</i></td>
 
942
                            <td><i>[Optional]</i> Width of the movie in pixels (Maximum: 1920).</td>
 
943
                        </tr>
 
944
                        <tr>
 
945
                            <td><b>height</b></td>
 
946
                            <td><i>Integer</i></td>
 
947
                            <td><i>[Optional]</i> Height of the movie in pixels (Maximum: 1200).</td>
 
948
                        </tr>
 
949
                        <tr>
 
950
                            <td><b>numFrames</b></td>
 
951
                            <td><i>Integer</i></td>
 
952
                            <td><i>[Optional]</i>The maximum number of frames that will be used during movie creation. 
 
953
                                    You may have between 10 and 300 frames. The default value is 300.
 
954
                            </td>
 
955
                        </tr>
 
956
                        <tr>
 
957
                            <td><b>frameRate</b></td>
 
958
                            <td><i>Float</i></td>
 
959
                            <td><i>[Optional]</i>The number of frames per second. The default value is 15.</td>
 
960
                        </tr>
 
961
                        <tr>
 
962
                            <td><b>movieLength</b></td>
 
963
                            <td><i>Float</i></td>
 
964
                            <td><i>[Optional]</i>The length in seconds of the video to be produced.</td>
 
965
                        </tr>
 
966
                        <tr>
 
967
                            <td><b>watermark</b></td>
 
968
                            <td><i>Boolean</i></td>
 
969
                            <td><i>[Optional]</i> Enables turning watermarking on or off. If watermark is set to false, the images will not be watermarked, 
 
970
                                which will speed up movie generation time but you will have no timestamps on the movie. If left blank, it defaults to true 
 
971
                                and images will be watermarked.</td>
 
972
                        </tr>
 
973
                        <tr>
 
974
                            <td><b>display</b></td>
 
975
                            <td><i>Boolean</i></td>
 
976
                            <td><i>[Optional]</i> If display is true, the movie will display on the page when it is ready. If display is false, the
 
977
                                filepath to the movie's flash-format file will be returned as JSON. If display is not specified, it will default to true.</td>
 
978
                        </tr>
 
979
                    </tbody>
 
980
                </table>
 
981
                
 
982
                <br />
 
983
                Result:
 
984
                <br /><br />
 
985
                The result includes an identifier for the movie request and an estimated time before the movie is ready to be downloaded.
 
986
                <br /><br />
 
987
                
 
988
                <table class="param-list" cellspacing="10">
 
989
                    <tbody valign="top">
 
990
                        <tr>
 
991
                            <td width="20%"><b>id</b></td>
 
992
                            <td width="25%"><i>String</i></td>
 
993
                            <td width="55%">Movie identifier</td>
 
994
                        </tr>
 
995
                        <tr>
 
996
                            <td><b>eta</b></td>
 
997
                            <td><i>Integer</i></td>
 
998
                            <td>The estimated time in seconds until the movie has been processed.</td>
 
999
                        </tr>
 
1000
 
 
1001
                    </tbody>
 
1002
                </table>
 
1003
                <br />
 
1004
                
 
1005
                <span class="example-header">Examples:</span>
 
1006
                <span class="example-url">
 
1007
                <a href="<?php echo HV_API_ROOT_URL;?>?action=queueMovie&startTime=2010-03-01T12:12:12Z&endTime=2010-03-02T12:12:12Z&imageScale=21.04&layers=[3,1,100],[4,1,100]&x1=-5000&y1=-5000&x2=5000&y2=5000">
 
1008
                    <?php echo HV_API_ROOT_URL;?>?action=queueMovie&startTime=2010-03-01T12:12:12Z&endTime=2010-03-04T12:12:12Z&imageScale=21.04&layers=[3,1,100],[4,1,100]&x1=-5000&y1=-5000&x2=5000&y2=5000
 
1009
                </a>
 
1010
                </span><br />
 
1011
                <span class="example-url">
 
1012
                <a href="<?php echo HV_API_ROOT_URL;?>?action=queueMovie&startTime=2010-03-01T12:12:12Z&endTime=2010-03-02T12:12:12Z&imageScale=21.04&layers=[SOHO,EIT,EIT,304,1,100],[SOHO,LASCO,C2,white-light,1,100]&x1=-5000&y1=-5000&x2=5000&y2=5000">
 
1013
                    <?php echo HV_API_ROOT_URL;?>?action=queueMovie&startTime=2010-03-01T12:12:12Z&endTime=2010-03-04T12:12:12Z&imageScale=21.04&layers=[SOHO,EIT,EIT,304,1,100],[SOHO,LASCO,C2,white-light,1,100]&x1=-5000&y1=-5000&x2=5000&y2=5000
 
1014
                </a>
 
1015
                </span><br />
 
1016
                <!--
 
1017
                <span class="example-url">
 
1018
                <i>iPod Video:</i><br /><br />
 
1019
                <a href="<?php echo HV_API_ROOT_URL;?>?action=queueMovie&startTime=2010-03-01T12:12:12Z&endTime=2010-03-02T12:12:12Z&imageScale=8.416&layers=[1,1,100]&x1=-1347&y1=-1347&x2=1347&y2=1347&display=false&watermark=false">
 
1020
                    <?php echo HV_API_ROOT_URL;?>?action=queueMovie&startTime=2010-03-01T12:12:12Z&endTime=2010-03-04T12:12:12Z&imageScale=8.416&layers=[1,1,100]&x1=-1347&y1=-1347&x2=1347&y2=1347&display=false&watermark=false
 
1021
                </a>
 
1022
                </span>
 
1023
                 -->
 
1024
                </div>
 
1025
            </div>
 
1026
        
 
1027
            <br />
 
1028
            <br />      
 
1029
        </div>
 
1030
        <?php
 
1031
    }
 
1032
}