~jstys-z/helioviewer.org/client5

« back to all changes in this revision

Viewing changes to src/php/Movie/HelioviewerMovie.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
 
 * Movie_HelioviewerMovie Class Definition
5
 
 *
6
 
 * 2011/05/24:
7
 
 *     http://flowplayer.org/plugins/streaming/pseudostreaming.html#prepare
8
 
 *
9
 
 * PHP version 5
10
 
 *
11
 
 * @category Movie
12
 
 * @package  Helioviewer
13
 
 * @author   Jeff Stys <jeff.stys@nasa.gov>
14
 
 * @author   Keith Hughitt <keith.hughitt@nasa.gov>
15
 
 * @author   Jaclyn Beck <jaclyn.r.beck@gmail.com>
16
 
 * @license  http://www.mozilla.org/MPL/MPL-1.1.html Mozilla Public License 1.1
17
 
 * @link     http://launchpad.net/helioviewer.org
18
 
 */
19
 
require_once HV_ROOT_DIR . '/../lib/alphaID/alphaID.php';
20
 
require_once HV_ROOT_DIR . '/src/php/Database/ImgIndex.php';
21
 
require_once HV_ROOT_DIR . '/src/php/Helper/DateTimeConversions.php';
22
 
require_once HV_ROOT_DIR . '/src/php/Helper/HelioviewerEvents.php';
23
 
require_once HV_ROOT_DIR . '/src/php/Helper/HelioviewerLayers.php';
24
 
require_once HV_ROOT_DIR . '/src/php/Helper/RegionOfInterest.php';
25
 
require_once HV_ROOT_DIR . '/src/php/Helper/Serialize.php';
26
 
/**
27
 
 * Represents a static (e.g. mp4/webm) movie generated by Helioviewer
28
 
 *
29
 
 * Note: For movies, it is easiest to work with Unix timestamps since that
30
 
 *       is what is returned from the database. To get from a javascript
31
 
 *       Date object to a Unix timestamp, simply use
32
 
 *       "date.getTime() * 1000."
33
 
 *       (getTime returns the number of miliseconds)
34
 
 *
35
 
 * Movie Status:
36
 
 *  0   QUEUED
37
 
 *  1   PROCESSING
38
 
 *  2   COMPLETED
39
 
 *  3   ERROR
40
 
 *
41
 
 * @category Movie
42
 
 * @package  Helioviewer
43
 
 * @author   Jeff Stys <jeff.stys@nasa.gov>
44
 
 * @author   Keith Hughitt <keith.hughitt@nasa.gov>
45
 
 * @author   Jaclyn Beck <jaclyn.r.beck@gmail.com>
46
 
 * @license  http://www.mozilla.org/MPL/MPL-1.1.html Mozilla Public License 1.1
47
 
 * @link     http://launchpad.net/helioviewer.org
48
 
 */
49
 
class Movie_HelioviewerMovie {
50
 
 
51
 
    public $id;
52
 
    public $frameRate;
53
 
    public $movieLength;
54
 
    public $maxFrames;
55
 
    public $numFrames;
56
 
    public $reqStartDate;
57
 
    public $reqEndDate;
58
 
    public $startDate;
59
 
    public $endDate;
60
 
    public $width;
61
 
    public $height;
62
 
    public $directory;
63
 
    public $filename;
64
 
    public $format;
65
 
    public $status;
66
 
    public $timestamp;
67
 
    public $modified;
68
 
    public $watermark;
69
 
    public $eventsLabels;
70
 
    public $scale;
71
 
    public $scaleType;
72
 
    public $scaleX;
73
 
    public $scaleY;
74
 
 
75
 
    private $_db;
76
 
    private $_layers;
77
 
    private $_events;
78
 
    private $_roi;
79
 
    private $_timestamps = array();
80
 
    private $_frames     = array();
81
 
 
82
 
    private $_cachedir;
83
 
    private $_filename;
84
 
    private $_filepath;
85
 
    private $_cached = false;
86
 
 
87
 
    /**
88
 
     * Prepares the parameters passed in from the api call and makes a
89
 
     * movie from them.
90
 
     *
91
 
     * @return {String} a url to the movie, or the movie will display.
92
 
     */
93
 
    public function __construct($publicId, $format='mp4') {
94
 
        $this->_db       = false;
95
 
 
96
 
        $this->_cachedir = 'api/HelioviewerMovie';
97
 
        $this->_filename = urlencode($publicId.'.cache');
98
 
        $this->_filepath = $this->_cachedir.'/'.$this->_filename;
99
 
 
100
 
        if ( HV_DISABLE_CACHE !== true ) {
101
 
            $cache = new Helper_Serialize($this->_cachedir,
102
 
                $this->_filename, 60);
103
 
            $info = $cache->readCache($verifyAge=true);
104
 
            if ( $info !== false ) {
105
 
                $this->_cached = true;
106
 
            }
107
 
        }
108
 
 
109
 
        if ( $this->_cached !== true ) {
110
 
            $info = $this->_loadFromDB($publicId, $format);
111
 
            if ( HV_DISABLE_CACHE !== true ) {
112
 
                if ( $cache->writeCache($info) ) {
113
 
                    $this->_cached = true;
114
 
                }
115
 
            }
116
 
        }
117
 
 
118
 
        $this->publicId     = $publicId;
119
 
        $this->format       = $format;
120
 
        $this->reqStartDate = $info['reqStartDate'];
121
 
        $this->reqEndDate   = $info['reqEndDate'];
122
 
        $this->startDate    = $info['startDate'];
123
 
        $this->endDate      = $info['endDate'];
124
 
        $this->timestamp    = $info['timestamp'];
125
 
        $this->modified     = $info['modified'];
126
 
        $this->imageScale   = (float)$info['imageScale'];
127
 
        $this->frameRate    = (float)$info['frameRate'];
128
 
        $this->movieLength  = (float)$info['movieLength'];
129
 
        $this->id           = (int)alphaID($publicId,true,5,HV_MOVIE_ID_PASS);
130
 
        $this->status       = (int)$info['status'];
131
 
        $this->numFrames    = (int)$info['numFrames'];
132
 
        $this->width        = (int)$info['width'];
133
 
        $this->height       = (int)$info['height'];
134
 
        $this->watermark    = (bool)$info['watermark'];
135
 
        $this->eventsLabels = (bool)$info['eventsLabels'];
136
 
        $this->scale        = (bool)$info['scale'];
137
 
        $this->scaleType    = $info['scaleType'];
138
 
        $this->scaleX       = (float)$info['scaleX'];
139
 
        $this->scaleY       = (float)$info['scaleY'];
140
 
        $this->maxFrames    = min((int)$info['maxFrames'],HV_MAX_MOVIE_FRAMES);
141
 
 
142
 
        // Data Layers
143
 
        $this->_layers = new Helper_HelioviewerLayers(
144
 
            $info['dataSourceString']);
145
 
        $this->_events = new Helper_HelioviewerEvents(
146
 
            $info['eventSourceString']);
147
 
 
148
 
        // Regon of interest
149
 
        $this->_roi = Helper_RegionOfInterest::parsePolygonString(
150
 
            $info['roi'], $info['imageScale']);
151
 
    }
152
 
 
153
 
    private function _dbSetup() {
154
 
        if ( $this->_db === false ) {
155
 
            $this->_db = new Database_ImgIndex();
156
 
        }
157
 
    }
158
 
 
159
 
 
160
 
    private function _loadFromDB($publicId, $format) {
161
 
        $this->_dbSetup();
162
 
 
163
 
        $id   = alphaID($publicId, true, 5, HV_MOVIE_ID_PASS);
164
 
        $info = $this->_db->getMovieInformation($id);
165
 
 
166
 
        if ( is_null($info) ) {
167
 
            throw new Exception('Unable to find the requested movie: '.$id,24);
168
 
        }
169
 
 
170
 
        return $info;
171
 
    }
172
 
 
173
 
 
174
 
    /**
175
 
     * Build the movie frames and movie
176
 
     */
177
 
    public function build() {
178
 
        $this->_dbSetup();
179
 
 
180
 
        date_default_timezone_set('UTC');
181
 
 
182
 
        if ( $this->status == 2 ) {
183
 
            return;
184
 
        }
185
 
 
186
 
        $this->_db->markMovieAsProcessing($this->id, 'mp4');
187
 
 
188
 
        try {
189
 
            $this->directory = $this->_buildDir();
190
 
 
191
 
            // If the movie frames have not been built create them
192
 
            if ( !@file_exists($this->directory.'frames') ) {
193
 
                require_once HV_ROOT_DIR .
194
 
                    '/src/php/Image/Composite/HelioviewerMovieFrame.php';
195
 
 
196
 
                $t1 = date('Y-m-d H:i:s');
197
 
 
198
 
                // Get timestamps for frames in the key movie layer
199
 
                $this->_getTimeStamps();
200
 
 
201
 
                // Set the actual start and end dates, frame-rate,
202
 
                // movie length, numFrames and dimensions
203
 
                $this->_setMovieProperties();
204
 
 
205
 
                // Build movie frames
206
 
                $this->_buildMovieFrames($this->watermark);
207
 
 
208
 
                $t2 = date('Y-m-d H:i:s');
209
 
 
210
 
                // Update status and log time to build frames
211
 
                $this->_db->finishedBuildingMovieFrames($this->id, $t1, $t2);
212
 
            }
213
 
            else {
214
 
                $this->filename = $this->_buildFilename();
215
 
            }
216
 
        }
217
 
        catch (Exception $e) {
218
 
            $this->_abort('Error encountered during movie frame compilation: '
219
 
                . $e->getMessage() );
220
 
        }
221
 
 
222
 
        $t3 = time();
223
 
 
224
 
        // Compile movie
225
 
        try {
226
 
            $this->_encodeMovie();
227
 
        }
228
 
        catch (Exception $e) {
229
 
            $t4 = time();
230
 
            $this->_abort('Error encountered during video encoding. ' .
231
 
                'This may be caused by an FFmpeg configuration issue, ' .
232
 
                'or by insufficient permissions in the cache.', $t4 - $t3);
233
 
        }
234
 
 
235
 
        // Log buildMovie in statistics table
236
 
        if ( HV_ENABLE_STATISTICS_COLLECTION ) {
237
 
            include_once HV_ROOT_DIR.'/src/php/Database/Statistics.php';
238
 
 
239
 
            $statistics = new Database_Statistics();
240
 
            $statistics->log('buildMovie');
241
 
        }
242
 
 
243
 
        $this->_cleanUp();
244
 
    }
245
 
 
246
 
    /**
247
 
     * Returns information about the completed movie
248
 
     *
249
 
     * @return array A list of movie properties and a URL to the finished movie
250
 
     */
251
 
    public function getCompletedMovieInformation($verbose=false) {
252
 
 
253
 
        $info = array(
254
 
            'frameRate'  => $this->frameRate,
255
 
            'numFrames'  => $this->numFrames,
256
 
            'startDate'  => $this->startDate,
257
 
            'status'     => $this->status,
258
 
            'endDate'    => $this->endDate,
259
 
            'width'      => $this->width,
260
 
            'height'     => $this->height,
261
 
            'title'      => $this->getTitle(),
262
 
            'thumbnails' => $this->getPreviewImages(),
263
 
            'url'        => $this->getURL()
264
 
        );
265
 
 
266
 
        if ($verbose) {
267
 
            $extra = array(
268
 
                'timestamp'  => $this->timestamp,
269
 
                'duration'   => $this->getDuration(),
270
 
                'imageScale' => $this->imageScale,
271
 
                'layers'     => $this->_layers->serialize(),
272
 
                'events'     => $this->_events->serialize(),
273
 
                'x1'         => $this->_roi->left(),
274
 
                'y1'         => $this->_roi->top(),
275
 
                'x2'         => $this->_roi->right(),
276
 
                'y2'         => $this->_roi->bottom()
277
 
            );
278
 
            $info = array_merge($info, $extra);
279
 
        }
280
 
 
281
 
        return $info;
282
 
    }
283
 
 
284
 
    /**
285
 
     * Returns an array of filepaths to the movie's preview images
286
 
     */
287
 
    public function getPreviewImages() {
288
 
        $rootURL = str_replace(HV_CACHE_DIR, HV_CACHE_URL, $this->_buildDir());
289
 
 
290
 
        $images = array();
291
 
 
292
 
        foreach ( array('icon', 'small', 'medium', 'large', 'full') as $size) {
293
 
            $images[$size] = $rootURL.'preview-'.$size.'.png';
294
 
        }
295
 
 
296
 
        return $images;
297
 
    }
298
 
 
299
 
    /**
300
 
     * Returns the base filepath for movie without any file extension
301
 
     */
302
 
    public function getFilepath($highQuality=false) {
303
 
        return $this->_buildDir().$this->_buildFilename($highQuality);
304
 
    }
305
 
 
306
 
    public function getDuration() {
307
 
        return $this->numFrames / $this->frameRate;
308
 
    }
309
 
 
310
 
    public function getURL() {
311
 
        return str_replace(HV_CACHE_DIR, HV_CACHE_URL, $this->_buildDir()) .
312
 
            $this->_buildFilename();
313
 
    }
314
 
 
315
 
    public function getCurrentFrame() {
316
 
        $dir = dirname($this->getFilepath()) . '/frames/';
317
 
        $pattern = '/^frame([0-9]{1,4})\.bmp$/';
318
 
        $newest_timestamp = 0;
319
 
        $newest_file = '';
320
 
        $newest_frame = '';
321
 
 
322
 
        if ($handle = @opendir($dir)) {
323
 
            while ( ($fname = readdir($handle)) !== false )  {
324
 
                // Skip current and parent directories ('.', '..')
325
 
                if ( preg_match('/^\.{1,2}$/', $fname) ) {
326
 
                    continue;
327
 
                }
328
 
 
329
 
                // Skip non-matching file patterns
330
 
                if ( !preg_match($pattern, $fname, $matches) ) {
331
 
                    continue;
332
 
                }
333
 
 
334
 
                $timedat = @filemtime($dir.'/'.$fname);
335
 
 
336
 
                if ($timedat > $newest_timestamp) {
337
 
                    $newest_timestamp = $timedat;
338
 
                    $newest_file = $fname;
339
 
                    $newest_frame = $matches[1];
340
 
                }
341
 
            }
342
 
        }
343
 
        @closedir($handle);
344
 
 
345
 
        return ($newest_frame>0) ? (int)$newest_frame : null;
346
 
    }
347
 
 
348
 
    /**
349
 
     * Cancels movie request
350
 
     *
351
 
     * @param string $msg Error message
352
 
     */
353
 
    private function _abort($msg, $procTime=0) {
354
 
        $this->_dbSetup();
355
 
        $this->_db->markMovieAsInvalid($this->id, $procTime);
356
 
        $this->_cleanUp();
357
 
 
358
 
        throw new Exception('Unable to create movie: '.$msg);
359
 
    }
360
 
 
361
 
    /**
362
 
     * Determines the directory to store the movie in.
363
 
     *
364
 
     * @return string Directory
365
 
     */
366
 
    private function _buildDir() {
367
 
        $date = str_replace('-', '/', substr($this->timestamp, 0, 10));
368
 
 
369
 
        return sprintf('%s/movies/%s/%s/', HV_CACHE_DIR, $date,
370
 
            $this->publicId);
371
 
    }
372
 
 
373
 
    /**
374
 
     * Determines filename to use for the movie
375
 
     *
376
 
     * @param string $extension Extension of the movie format to be created
377
 
     *
378
 
     * @return string Movie filename
379
 
     */
380
 
    private function _buildFilename($highQuality=false) {
381
 
        $start = str_replace(array(':', '-', ' '), '_', $this->startDate);
382
 
        $end   = str_replace(array(':', '-', ' '), '_', $this->endDate);
383
 
 
384
 
        $suffix = ($highQuality && $this->format == 'mp4') ? '-hq' : '';
385
 
 
386
 
        return sprintf("%s_%s_%s%s.%s", $start, $end,
387
 
            $this->_layers->toString(), $suffix, $this->format);
388
 
    }
389
 
 
390
 
    /**
391
 
     * Takes in meta and layer information and creates movie frames from them.
392
 
     *
393
 
     * TODO: Use middle frame instead last one...
394
 
     * TODO: Create standardized thumbnail sizes
395
 
     *       (e.g. thumbnail-med.png = 480x320, etc)
396
 
     *
397
 
     * @return $images an array of built movie frames
398
 
     */
399
 
    private function _buildMovieFrames($watermark) {
400
 
        $this->_dbSetup();
401
 
 
402
 
        $frameNum = 0;
403
 
 
404
 
        // Movie frame parameters
405
 
        $options = array(
406
 
            'database'  => $this->_db,
407
 
            'compress'  => false,
408
 
            'interlace' => false,
409
 
            'watermark' => $watermark
410
 
        );
411
 
 
412
 
        // Index of preview frame
413
 
        $previewIndex = floor($this->numFrames/2);
414
 
 
415
 
        // Add tolerance for single-frame failures
416
 
        $numFailures = 0;
417
 
 
418
 
        // Compile frames
419
 
        foreach ($this->_timestamps as $time) {
420
 
 
421
 
            $filepath =  sprintf('%sframes/frame%d.bmp', $this->directory,
422
 
                $frameNum);
423
 
 
424
 
            try {
425
 
                $screenshot = new Image_Composite_HelioviewerMovieFrame(
426
 
                    $filepath, $this->_layers, $this->_events,
427
 
                    $this->eventsLabels, $this->scale, $this->scaleType,
428
 
                    $this->scaleX, $this->scaleY, $time, $this->_roi,
429
 
                    $options);
430
 
 
431
 
                if ( $frameNum == $previewIndex ) {
432
 
                    // Make a copy of frame to be used for preview images
433
 
                    $previewImage = $screenshot;
434
 
                }
435
 
 
436
 
                $frameNum++;
437
 
                array_push($this->_frames, $filepath);
438
 
            }
439
 
            catch (Exception $e) {
440
 
                $numFailures += 1;
441
 
 
442
 
                if ($numFailures <= 3) {
443
 
                    // Recover if failure occurs on a single frame
444
 
                    $this->numFrames--;
445
 
                }
446
 
                else {
447
 
                    // Otherwise proprogate exception to be logged
448
 
                    throw $e;
449
 
                }
450
 
            }
451
 
        }
452
 
 
453
 
        $this->_createPreviewImages($previewImage);
454
 
    }
455
 
 
456
 
    /**
457
 
     * Remove movie frames and directory
458
 
     *
459
 
     * @return void
460
 
     */
461
 
    private function _cleanUp() {
462
 
        $dir = $this->directory.'frames/';
463
 
 
464
 
        // Clean up movie frame images that are no longer needed
465
 
        if ( @file_exists($dir) ) {
466
 
            foreach (glob("$dir*") as $image) {
467
 
                @unlink($image);
468
 
            }
469
 
            @rmdir($dir);
470
 
        }
471
 
    }
472
 
 
473
 
    /**
474
 
     * Creates preview images of several different sizes
475
 
     */
476
 
    private function _createPreviewImages(&$screenshot) {
477
 
 
478
 
        // Create preview image
479
 
        $preview = $screenshot->getIMagickImage();
480
 
        $preview->setImageCompression(IMagick::COMPRESSION_LZW);
481
 
        $preview->setImageCompressionQuality(PNG_LOW_COMPRESSION);
482
 
        $preview->setInterlaceScheme(IMagick::INTERLACE_PLANE);
483
 
 
484
 
        // Thumbnail sizes to create
485
 
        $sizes = array(
486
 
            'large'  => array(640, 480),
487
 
            'medium' => array(320, 240),
488
 
            'small'  => array(240, 180),
489
 
            'icon'   => array( 64,  64)
490
 
        );
491
 
 
492
 
        foreach ($sizes as $name=>$dimensions) {
493
 
            $thumb = clone $preview;
494
 
            $thumb->thumbnailImage($dimensions[0], $dimensions[1], true);
495
 
 
496
 
            // Add black border to reach desired preview image sizes
497
 
            $borderWidth  = ceil(($dimensions[0]-$thumb->getImageWidth()) /2);
498
 
            $borderHeight = ceil(($dimensions[1]-$thumb->getImageHeight())/2);
499
 
 
500
 
            $thumb->borderImage('black', $borderWidth, $borderHeight);
501
 
            $thumb->cropImage($dimensions[0], $dimensions[1], 0, 0);
502
 
 
503
 
            $thumb->writeImage($this->directory.'preview-'.$name.'.png');
504
 
            $thumb->destroy();
505
 
        }
506
 
 
507
 
        $preview->writeImage($this->directory.'preview-full.png');
508
 
        $preview->destroy();
509
 
    }
510
 
 
511
 
    /**
512
 
     * Builds the requested movie
513
 
     *
514
 
     * Makes a temporary directory to store frames in, calculates a timestamp
515
 
     * for every frame, gets the closest image to each timestamp for each
516
 
     * layer. Then takes all layers belonging to one timestamp and makes a
517
 
     * movie frame out of it. When done with all movie frames, phpvideotoolkit
518
 
     * is used to compile all the frames into a movie.
519
 
     *
520
 
     * @return void
521
 
     */
522
 
    private function _encodeMovie() {
523
 
 
524
 
        require_once HV_ROOT_DIR.'/src/php/Movie/FFMPEGEncoder.php';
525
 
 
526
 
        // Compute movie meta-data
527
 
        $layerString = $this->_layers->toHumanReadableString();
528
 
 
529
 
        // Date string
530
 
        $dateString = $this->getDateString();
531
 
 
532
 
        // URLS
533
 
        $url1 = HV_WEB_ROOT_URL . '/?action=playMovie&id='
534
 
                                . $this->publicId.'&format='.$this->format
535
 
                                . '&hq=true';
536
 
        $url2 = HV_WEB_ROOT_URL . '/?action=downloadMovie&id='
537
 
                                . $this->publicId.'&format='.$this->format
538
 
                                . '&hq=true';
539
 
 
540
 
        // Title
541
 
        $title = sprintf('%s (%s)', $layerString, $dateString);
542
 
 
543
 
        // Description
544
 
        $description = sprintf(
545
 
            'The Sun as seen through %s from %s.',
546
 
            $layerString, str_replace('-', ' to ', $dateString)
547
 
        );
548
 
 
549
 
        // Comment
550
 
        $comment = sprintf(
551
 
            'This movie was produced by Helioviewer.org. See the original ' .
552
 
            'at %s or download a high-quality version from %s.', $url1, $url2
553
 
        );
554
 
 
555
 
        // MP4 filename
556
 
        $filename = str_replace('webm', 'mp4', $this->filename);
557
 
 
558
 
        // Limit frame-rate floating-point precision
559
 
        // https://bugs.launchpad.net/helioviewer.org/+bug/979231
560
 
        $frameRate = round($this->frameRate, 1);
561
 
 
562
 
        // Create and FFmpeg encoder instance
563
 
        $ffmpeg = new Movie_FFMPEGEncoder(
564
 
            $this->directory, $filename, $frameRate, $this->width,
565
 
            $this->height, $title, $description, $comment
566
 
        );
567
 
 
568
 
        $this->_dbSetup();
569
 
 
570
 
 
571
 
        // Create H.264 videos
572
 
        $t1 = time();
573
 
        $ffmpeg->setFormat('mp4');
574
 
        $ffmpeg->createVideo();
575
 
        $ffmpeg->createHQVideo();
576
 
        $ffmpeg->createFlashVideo();
577
 
 
578
 
        // Mark mp4 movie as completed
579
 
        $t2 = time();
580
 
        $this->_db->markMovieAsFinished($this->id, 'mp4', $t2 - $t1);
581
 
 
582
 
 
583
 
        // Create a low-quality webm movie for in-browser use if requested
584
 
        $t3 = time();
585
 
        $ffmpeg->setFormat('webm');
586
 
        $ffmpeg->createVideo();
587
 
 
588
 
        // Mark movie as completed
589
 
        $t4 = time();
590
 
        $this->_db->markMovieAsFinished($this->id, 'webm', $t4 - $t3);
591
 
    }
592
 
 
593
 
    /**
594
 
     * Returns a human-readable title for the video
595
 
     */
596
 
    public function getTitle() {
597
 
        date_default_timezone_set('UTC');
598
 
 
599
 
        $layerString = $this->_layers->toHumanReadableString();
600
 
        $dateString  = $this->getDateString();
601
 
 
602
 
        return sprintf('%s (%s)', $layerString, $dateString);
603
 
    }
604
 
 
605
 
    /**
606
 
     * Returns a human-readable date string
607
 
     */
608
 
    public function getDateString() {
609
 
        date_default_timezone_set('UTC');
610
 
 
611
 
        if (substr($this->startDate, 0, 9) == substr($this->endDate, 0, 9)) {
612
 
            $endDate = substr($this->endDate, 11);
613
 
        }
614
 
        else {
615
 
            $endDate = $this->endDate;
616
 
        }
617
 
 
618
 
        return sprintf('%s - %s UTC', $this->startDate, $endDate);
619
 
    }
620
 
 
621
 
    /**
622
 
     * Returns an array of the timestamps for the key movie layer
623
 
     *
624
 
     * For single layer movies, the number of frames will be either
625
 
     * HV_MAX_MOVIE_FRAMES, or the number of images available for the
626
 
     * requested time range. For multi-layer movies, the number of frames
627
 
     * included may be reduced to ensure that the total number of
628
 
     * SubFieldImages needed does not exceed HV_MAX_MOVIE_FRAMES
629
 
     */
630
 
    private function _getTimeStamps() {
631
 
        $this->_dbSetup();
632
 
 
633
 
        $layerCounts = array();
634
 
 
635
 
        // Determine the number of images that are available for the request
636
 
        // duration for each layer
637
 
        foreach ($this->_layers->toArray() as $layer) {
638
 
            $n = $this->_db->getDataCount($this->reqStartDate,
639
 
                $this->reqEndDate, $layer['sourceId']);
640
 
 
641
 
            $layerCounts[$layer['sourceId']] = $n;
642
 
        }
643
 
 
644
 
        // Choose the maximum number of frames that can be generated without
645
 
        // exceeded the server limits defined by HV_MAX_MOVIE_FRAMES
646
 
        $numFrames       = 0;
647
 
        $imagesRemaining = $this->maxFrames;
648
 
        $layersRemaining = $this->_layers->length();
649
 
 
650
 
        // Sort counts from smallest to largest
651
 
        asort($layerCounts);
652
 
 
653
 
        // Determine number of frames to create
654
 
        foreach($layerCounts as $dataSource => $count) {
655
 
            $numFrames = min($count, ($imagesRemaining / $layersRemaining));
656
 
            $imagesRemaining -= $numFrames;
657
 
            $layersRemaining -= 1;
658
 
        }
659
 
 
660
 
        // Number of frames to use
661
 
        $numFrames = floor($numFrames);
662
 
 
663
 
        // Get the entire range of available images between the movie start
664
 
        // and end time
665
 
        $entireRange = $this->_db->getDataRange($this->reqStartDate,
666
 
            $this->reqEndDate, $dataSource);
667
 
 
668
 
        // Sub-sample range so that only $numFrames timestamps are returned
669
 
        for ($i = 0; $i < $numFrames; $i++) {
670
 
            $index = round($i * (sizeOf($entireRange) / $numFrames));
671
 
            array_push($this->_timestamps, $entireRange[$index]['date']);
672
 
        }
673
 
    }
674
 
 
675
 
    /**
676
 
     * Determines dimensions to use for movie and stores them
677
 
     *
678
 
     * @return void
679
 
     */
680
 
    private function _setMovieDimensions() {
681
 
        $this->width  = round($this->_roi->getPixelWidth());
682
 
        $this->height = round($this->_roi->getPixelHeight());
683
 
 
684
 
        // Width and height must be divisible by 2 or ffmpeg will throw
685
 
        // an error.
686
 
        if ($this->width % 2 === 1) {
687
 
            $this->width += 1;
688
 
        }
689
 
 
690
 
        if ($this->height % 2 === 1) {
691
 
            $this->height += 1;
692
 
        }
693
 
    }
694
 
 
695
 
    /**
696
 
     * Determines some of the movie details and saves them to the database
697
 
     * record
698
 
     */
699
 
    private function _setMovieProperties() {
700
 
        $this->_dbSetup();
701
 
 
702
 
        // Store actual start and end dates that will be used for the movie
703
 
        $this->startDate = $this->_timestamps[0];
704
 
        $this->endDate   = $this->_timestamps[sizeOf($this->_timestamps) - 1];
705
 
 
706
 
        $this->filename = $this->_buildFilename();
707
 
 
708
 
        $this->numFrames = sizeOf($this->_timestamps);
709
 
 
710
 
        if ($this->numFrames == 0) {
711
 
            $this->_abort('No images available for the requested time range');
712
 
        }
713
 
 
714
 
        if ($this->frameRate) {
715
 
            $this->movieLength = $this->numFrames / $this->frameRate;
716
 
        }
717
 
        else {
718
 
            $this->frameRate = min(30, max(1,
719
 
                $this->numFrames / $this->movieLength) );
720
 
        }
721
 
 
722
 
        $this->_setMovieDimensions();
723
 
 
724
 
        // Update movie entry in database with new details
725
 
        $this->_db->storeMovieProperties(
726
 
            $this->id, $this->startDate, $this->endDate, $this->numFrames,
727
 
            $this->frameRate, $this->movieLength, $this->width, $this->height
728
 
        );
729
 
    }
730
 
 
731
 
    /**
732
 
     * Adds black border to movie frames if neccessary to guarantee a 16:9
733
 
     * aspect ratio
734
 
     *
735
 
     * Checks the ratio of width to height and adjusts each dimension so that
736
 
     * the ratio is 16:9. The movie will be padded with a black background in
737
 
     * JP2Image.php using the new width and height.
738
 
     *
739
 
     * @return array Width and Height of padded movie frames
740
 
     */
741
 
    private function _setAspectRatios() {
742
 
        $width  = $this->_roi->getPixelWidth();
743
 
        $height = $this->_roi->getPixelHeight();
744
 
 
745
 
        $ratio = $width / $height;
746
 
 
747
 
        // Adjust height if necessary
748
 
        if ( $ratio > 16/9 ) {
749
 
            $adjust  = (9/16) * $width / $height;
750
 
            $height *= $adjust;
751
 
        }
752
 
 
753
 
        $dimensions = array('width'  => $width,
754
 
                            'height' => $height);
755
 
 
756
 
        return $dimensions;
757
 
    }
758
 
 
759
 
    /**
760
 
     * Returns HTML for a video player with the requested movie loaded
761
 
     */
762
 
    public function getMoviePlayerHTML() {
763
 
 
764
 
        $filepath = str_replace(HV_ROOT_DIR, '../', $this->getFilepath());
765
 
        $css      = "width: {$this->width}px; height: {$this->height}px;";
766
 
        $duration = $this->numFrames / $this->frameRate;
767
 
?>
768
 
<!DOCTYPE html>
769
 
<html>
770
 
<head>
771
 
    <title>Helioviewer.org - <?php echo $this -> filename; ?></title>
772
 
    <script type="text/javascript" src="http://html5.kaltura.org/js"></script>
773
 
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js" type="text/javascript"></script>
774
 
</head>
775
 
<body>
776
 
<div style="text-align: center;">
777
 
    <div style="margin-left: auto; margin-right: auto; <?php echo $css; ?>";>
778
 
        <video style="margin-left: auto; margin-right: auto;" poster="<?php echo "$filepath.bmp"?>" durationHint="<?php echo $duration; ?>">
779
 
            <source src="<?php echo $filepath.'.mp4' ?>" />
780
 
            <source src="<?php echo $filepath.'.webm' ?>" />
781
 
            <source src="<?php echo $filepath.'.flv' ?>" />
782
 
        </video>
783
 
    </div>
784
 
</div>
785
 
</body>
786
 
</html>
787
 
<?php
788
 
    }
789
 
 
790
 
}
791
 
?>