2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
* Helioviewer Movies Module class definition
8
* @category Configuration
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
15
require_once 'interface.Module.php';
18
* Movie generation and display.
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
28
class Module_Movies implements Module
34
* Movie module constructor
36
* @param mixed &$params API request parameters
38
public function __construct(&$params)
40
$this->_params = $params;
41
$this->_options = array();
49
public function execute()
51
if ($this->validate()) {
53
$this->{$this->_params['action']}();
54
} catch (Exception $e) {
55
handleError($e->getMessage(), $e->getCode());
61
* Queues a request for a Helioviewer.org movie
63
public function queueMovie()
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';
73
$redis = new Redisent('localhost');
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);
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;
92
"movieLength" => NULL,
93
"maxFrames" => HV_MAX_MOVIE_FRAMES,
96
$options = array_replace($defaults, $this->_options);
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;
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);
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.
115
$roi = $this->_getMovieROI($options);
116
$roiString = $roi->getPolygonString();
118
$numPixels = $roi->getPixelWidth() * $roi->getPixelHeight();
120
// Use reduce image scale if necessary
121
$imageScale = $roi->imageScale();
123
// Max number of frames
124
$maxFrames = min($this->_getMaxFrames($queueSize), $options['maxFrames']);
126
// Create a connection to the database
127
$db = new Database_ImgIndex();
128
$movieDb = new Database_MovieDatabase();
130
// Estimate the number of frames
131
$numFrames = $this->_estimateNumFrames($db, $layers, $this->_params['startTime'], $this->_params['endTime']);
132
$numFrames = min($numFrames, $maxFrames);
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']);
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;
144
// Otherwise simply use the time estimated for the single movie
145
$eta = $estBuildTime;
146
$updateCounter = false;
149
// Get datasource bitmask
150
$bitmask = bindec($layers->getBitMask());
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']);
158
$publicId = alphaID($dbId, false, 5, HV_MOVIE_ID_PASS);
160
// Queue movie request
162
'movieId' => $publicId,
163
'eta' => $estBuildTime,
164
'format' => $options['format'],
165
'counter' => $updateCounter
167
$token = Resque::enqueue('on_demand_movie', 'Job_MovieBuilder', $args, true);
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);
178
"queue" => max(0, $queueSize + 1 - sizeOf($movieWorkers)),
182
$this->_printJSON(json_encode($response));
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
190
private function _estimateMovieBuildTime($movieDb, $numFrames, $numPixels, $format)
192
// Weights for influence of the number of frames/pixels on the
197
// Get stats for most recent 100 completed movie requests
198
$stats = $movieDb->getMovieStatistics();
200
// If no movie statistics have been collected yet, skip this step
201
if (sizeOf($stats['time']) === 0) {
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']);
209
// Estimate time to build movie frames
210
$frameEst = ($w1 * ($l1['m'] * $numFrames + $l1['b']) +
211
$w2 * ($l2['m'] * $numPixels + $l2['b']));
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)
219
$encodingEst = max(1, 0.066 * $numFrames + 0.778) +
220
max(1, 0.094 * $numFrames + 2.298);
222
// Scale by pixel area
223
$encodingEst = ($numPixels / (1920 * 1200)) * $encodingEst;
225
return (int) max(10, ($frameEst + $encodingEst));
229
* Linear regression function
231
* @param $x array x-coords
232
* @param $y array y-coords
234
* @returns array() m=>slope, b=>intercept
236
* http://richardathome.wordpress.com/2006/01/25/a-php-linear-regression-function/
238
private function _linear_regression($x, $y) {
242
$x_sum = array_sum($x);
243
$y_sum = array_sum($y);
248
for($i = 0; $i < $n; $i++) {
249
$xy_sum += ($x[$i] * $y[$i]);
250
$xx_sum += ($x[$i] * $x[$i]);
254
$divisor = (($n * $xx_sum) - ($x_sum * $x_sum));
259
$m = (($n * $xy_sum) - ($x_sum * $y_sum)) / $divisor;
262
// Calculate intercept
263
$b = ($y_sum - ($m * $x_sum)) / $n;
266
return array("m"=>$m, "b"=>$b);
270
* Determines the maximum number of frames allowed based on the current queue size
272
private function _getMaxFrames($queueSize)
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;
280
return HV_MAX_MOVIE_FRAMES;
284
* Returns the region of interest for the movie request or throws an error if one was not properly specified.
286
private function _getMovieROI($options) {
287
include_once 'src/Helper/RegionOfInterest.php';
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'];
302
throw new Exception("Region of interest not properly specified.", 23);
305
$roi = new Helper_RegionOfInterest($x1, $y1, $x2, $y2, $this->_params['imageScale']);
311
* Estimates the number of frames that a movie will include
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.
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
320
private function _estimateNumFrames($db, $layers, $startTime, $endTime)
323
$sql = "SELECT COUNT(*) FROM images WHERE DATE BETWEEN '%s' AND '%s' AND sourceId=%d;";
325
// Estimate number of movies frames for each layer
326
foreach($layers->toArray() as $layer) {
327
$numFrames += $db->getImageCount($startTime, $endTime, $layer['sourceId']);
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);
340
* Checks to see if the requested movie is available and if so returns
341
* it as a file-attachment
343
* @return file Requested movie
345
public function downloadMovie ()
347
include_once 'src/Movie/HelioviewerMovie.php';
350
$movie = new Movie_HelioviewerMovie($this->_params['id'],
351
$this->_params['format']);
357
$options = array_replace($defaults, $this->_options);
360
// If the movie is finished return the file as an attachment
361
if ($movie->status == 2) {
363
$filepath = $movie->getFilepath($options['hq']);
364
$filename = basename($filepath);
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']);
377
echo file_get_contents($filepath);
379
// Otherwise return an error
381
header('Content-type: application/json');
383
"error" => "The movie you requested is either being processed
386
print json_encode($response);
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
396
public function getMovieStatus ()
398
include_once 'src/Movie/HelioviewerMovie.php';
401
$movie = new Movie_HelioviewerMovie($this->_params['id'], $this->_params['format']);
402
$verbose = isset($this->_options['verbose']) ? $this->_options['verbose'] : false;
405
if ($movie->status == 2) {
406
$response = $movie->getCompletedMovieInformation($verbose);
407
} else if ($movie->status == 3) {
411
"error" => "Sorry, we are unable to create your movie at this time. Please try again later."
413
} else if ($movie->status == 0) {
415
if (isset($this->_options['token'])) {
416
require_once 'lib/Resque.php';
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');
429
"position" => $queueNum,
430
"total" => $queueSize
434
$response = array("status" => 0);
443
$this->_printJSON(json_encode($response));
447
* Determines the position of a job within a specified queue
449
* Note: https://github.com/chrisboulton/php-resque/issues/45
451
* @return int Returns queue position or else -1 if job not found
453
private function _getQueueNum($queue, $id) {
456
foreach (Resque::redis()->lrange("queue:$queue", 0, -1) as $job) {
457
if (strpos($job, $id) !== false) {
467
* Checks to see if Helioviewer.org is authorized to upload videos for a user
469
public function checkYouTubeAuth ()
471
include_once 'src/Movie/YouTube.php';
473
$youtube = new Movie_YouTube();
475
$this->_printJSON(json_encode($youtube->checkYouTubeAuth()));
479
* Requests authorization for Helioviewer.org to upload videos on behalf
482
public function getYouTubeAuth()
484
include_once 'src/Movie/YouTube.php';
486
$share = isset($this->_options['share']) ? $this->_options['share'] : false;
490
// Discard any existing authorization
491
unset($_SESSION['sessionToken']);
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;
500
$youtube = new Movie_YouTube();
501
$youtube->getYouTubeAuth($this->_params['id']);
505
* Uploads a user-created video to YouTube
507
* TODO 2011/05/09: Make sure movie hasn't already been uploaded
509
public function uploadMovieToYouTube ()
511
include_once 'src/Movie/HelioviewerMovie.php';
512
include_once 'src/Movie/YouTube.php';
515
$movie = new Movie_HelioviewerMovie($this->_params['id'], "mp4");
517
if ($movie->status !== 2) {
518
throw new Exception("Invalid movie requested", 41);
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;
530
// Otherwise read form data back in from session variables
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'];
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);
548
if (isset($this->_options['html']) && $this->_options['html']) {
554
$youtube = new Movie_YouTube();
555
$video = $youtube->uploadVideo($movie, $id, $title, $description, $tags, $share, $html);
559
* Retrieves recent user-submitted videos from YouTube and returns the
560
* result as a JSON array.
562
public function getUserVideos()
564
include_once 'src/Database/MovieDatabase.php';
565
include_once 'src/Movie/HelioviewerMovie.php';
566
include_once 'lib/alphaID/alphaID.php';
568
$movies = new Database_MovieDatabase();
573
"since" => '1000/01/01T00:00:00.000Z'
575
$opts = array_replace($defaults, $this->_options);
577
// Get a list of recent videos
580
foreach($movies->getSharedVideos($opts['num'], $opts['since']) as $video) {
581
$youtubeId = $video['youtubeId'];
582
$movieId = (int) $video['movieId'];
585
$publicId = alphaID($movieId, false, 5, HV_MOVIE_ID_PASS);
588
$movie = new Movie_HelioviewerMovie($publicId);
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);
595
// $response = curl_exec($handle);
596
// $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
598
//curl_close($handle);
600
// Only add videos with response code 200
601
//if ($httpCode == 200) {
602
array_push($videos, array(
604
"url" => "http://www.youtube.com/watch?v=$youtubeId",
605
"thumbnails" => $movie->getPreviewImages(),
606
"published" => $video['timestamp']
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");
617
$this->_printJSON(json_encode($videos));
621
* Generates HTML for a video player with the specified movie loaded
624
* Using direct links to movies to enable support for byte range requests
625
* and provide a better movie experience in Chromium.
627
* See: https://groups.google.com/a/webmproject.org/group/webm-discuss
628
* /browse_thread/thread/061f3ffa200d26a9/1ce5f06c76c9cb2d#1ce5f06c76c9cb2d
632
public function playMovie ()
634
include_once 'src/Movie/HelioviewerMovie.php';
637
$movie = new Movie_HelioviewerMovie($this->_params['id'],
638
$this->_params['format']);
643
"width" => $movie->width,
644
"height" => $movie->height
646
$options = array_replace($defaults, $this->_options);
648
// Return an error if movie is not available
649
if ($movie->status != 2) {
650
header('Content-type: application/json');
652
"error" => "The movie you requested is either being processed
655
print json_encode($response);
659
$dimensions = sprintf("width: %dpx; height: %dpx;",
660
$options['width'], $options['height']);
663
$filepath = $movie->getFilepath($options['hq']);
664
$filename = basename($filepath);
667
$url = str_replace(HV_ROOT_DIR, HV_WEB_ROOT_URL, $filepath);
672
<title>Helioviewer.org - <?php echo $filename?></title>
673
<script src="../lib/flowplayer/flowplayer-3.2.8.min.js"></script>
676
<!-- Movie player -->
677
<div href="<?php echo $url;?>"
678
style="display:block; <?php print $dimensions;?>"
682
<script language="javascript">
683
flowplayer("movie-player", "../lib/flowplayer/flowplayer-3.2.8.swf", {
696
* Helper function to output result as either JSON or JSONP
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
704
private function _printJSON($json, $xml=false, $utf=false)
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
711
$xmlStr = str_replace("\n", "", str_replace("'", "\'", $json));
712
$json = sprintf("%s('%s')", $this->_params['callback'], $xmlStr);
714
$json = sprintf("%s(%s)", $this->_params['callback'], $json);
718
// Set Content-type HTTP header
720
header('Content-type: application/json;charset=UTF-8');
722
header('Content-Type: application/json');
732
* @return bool Returns true if input parameters are valid
734
public function validate()
736
switch($this->_params['action'])
738
case "downloadMovie":
740
"required" => array('id', 'format'),
741
"optional" => array('hq'),
742
"alphanum" => array('id', 'format'),
743
"bools" => array('hq')
746
case "getMovieStatus":
748
"required" => array('id', 'format'),
749
"optional" => array('verbose', 'callback', 'token'),
750
"alphanum" => array('id', 'format', 'callback', 'token'),
751
"bools" => array('verbose')
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')
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')
775
case "uploadMovieToYouTube":
777
"required" => array('id'),
778
"optional" => array('title', 'description', 'tags', 'share', 'token', 'html'),
779
"alphanum" => array('id'),
780
"bools" => array('share', 'html')
783
case "getUserVideos":
785
"optional" => array('num', 'since', 'callback'),
786
"alphanum" => array('callback'),
787
"ints" => array('num'),
788
"dates" => array('since')
791
case "checkYouTubeAuth":
793
"optional" => array('callback'),
794
"alphanum" => array('callback')
797
case "getYouTubeAuth":
799
"required" => array('id', 'title', 'description', 'tags'),
800
"optional" => array('share'),
801
"alphanum" => array('id'),
802
"bools" => array('share')
809
if (isset($expected)) {
810
Validation_InputValidator::checkInput($expected, $this->_params, $this->_options);
817
* Prints the Movies module's documentation header
821
public static function printDocHeader()
825
<a href="index.php#MovieAPI">Movies</a>
827
<li><a href="index.php#queueMovie">Creating a Movie</a></li>
830
<li><a href="index.php#queueMovie">Check a Movie's Status</a></li>
833
<li><a href="index.php#queueMovie">Retrieving a Movie</a></li>
844
public static function printDoc()
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;">
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>
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>
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>
870
<div class="summary-box"><span
871
style="text-decoration: underline;">Usage:</span><br />
874
<?php echo HV_API_ROOT_URL;?>?action=queueMovie<br />
877
Supported Parameters:<br />
880
<table class="param-list" cellspacing="10">
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>
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>
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>
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>
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>
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>
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>
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>
931
<td><i>Float</i></td>
932
<td><i>[Optional]</i> The horizontal offset from the center of the Sun.</td>
936
<td><i>Float</i></td>
937
<td><i>[Optional]</i> The vertical offset from the center of the Sun.</td>
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>
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>
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.
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>
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>
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>
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>
985
The result includes an identifier for the movie request and an estimated time before the movie is ready to be downloaded.
988
<table class="param-list" cellspacing="10">
991
<td width="20%"><b>id</b></td>
992
<td width="25%"><i>String</i></td>
993
<td width="55%">Movie identifier</td>
997
<td><i>Integer</i></td>
998
<td>The estimated time in seconds until the movie has been processed.</td>
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
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
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