~jstys-z/helioviewer.org/client5

« back to all changes in this revision

Viewing changes to api/src/Movie/HelioviewerMovie.php

  • Committer: Keith Hughitt
  • Date: 2011-05-26 19:28:41 UTC
  • mfrom: (402.1.667 hv)
  • Revision ID: keith.hughitt@nasa.gov-20110526192841-5xmuft9jsm52pzih
Helioviewer.org 2.2.0 Release

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
3
/**
4
4
 * Movie_HelioviewerMovie Class Definition
 
5
 * 
 
6
 * 2011/05/24: http://flowplayer.org/plugins/streaming/pseudostreaming.html#prepare
5
7
 *
6
8
 * PHP version 5
7
9
 *
12
14
 * @license  http://www.mozilla.org/MPL/MPL-1.1.html Mozilla Public License 1.1
13
15
 * @link     http://launchpad.net/helioviewer.org
14
16
 */
15
 
require_once 'src/Image/Composite/HelioviewerCompositeImage.php';
 
17
require_once 'src/Helper/HelioviewerLayers.php';
 
18
require_once 'src/Helper/RegionOfInterest.php';
16
19
require_once 'src/Helper/DateTimeConversions.php';
17
20
require_once 'src/Database/ImgIndex.php';
18
 
require_once 'src/Movie/FFMPEGEncoder.php';
 
21
require_once 'lib/alphaID/alphaID.php';
 
22
 
19
23
/**
20
 
 * Represents a static (e.g. ogv/mp4) movie generated by Helioviewer
 
24
 * Represents a static (e.g. mp4/webm) movie generated by Helioviewer
21
25
 *
22
26
 * Note: For movies, it is easiest to work with Unix timestamps since that is what is returned
23
27
 *       from the database. To get from a javascript Date object to a Unix timestamp, simply
32
36
 */
33
37
class Movie_HelioviewerMovie
34
38
{
 
39
    public $id;
 
40
    public $frameRate;
 
41
    public $maxFrames;
 
42
    public $numFrames;
 
43
    public $reqStartDate;
 
44
    public $reqEndDate;
 
45
    public $startDate;
 
46
    public $endDate;
 
47
    public $width;
 
48
    public $height;
 
49
    public $directory;
 
50
    public $filename;
 
51
    public $format;
 
52
    public $status;
 
53
    public $timestamp;
 
54
    public $watermark;
 
55
 
35
56
    private $_db;
36
57
    private $_layers;
37
58
    private $_roi;
38
 
    private $_directory;
39
 
    private $_filename;
40
 
    private $_startTimestamp;
41
 
    private $_startDateString;
42
 
    private $_endTimestamp;
43
 
    private $_endDateString;
44
 
    private $_frames;
45
 
    private $_frameRate;
46
 
    private $_numFrames;
47
 
    
 
59
    private $_timestamps = array();
 
60
    private $_frames     = array();
 
61
 
48
62
    /**
49
63
     * Prepares the parameters passed in from the api call and makes a movie from them.
50
64
     *
51
65
     * @return {String} a url to the movie, or the movie will display.
52
66
     */
53
 
    public function __construct($layers, $startDateString, $endDateString, $roi, $options)
54
 
    {
55
 
        $defaults = array(
56
 
            'filename'    => false,
57
 
            'frameRate'   => false,
58
 
            'numFrames'   => false,
59
 
            'outputDir'   => "",
60
 
            'uuid'        => false,
61
 
            'watermarkOn' => true
62
 
        );
63
 
 
64
 
        $options = array_replace($defaults, $options);
65
 
 
66
 
        $this->_db        = new Database_ImgIndex();
67
 
        $this->_layers    = $layers;
68
 
        $this->_roi       = $roi;
69
 
        $this->_directory = $this->_createCacheDirectories($options['uuid'], $options['outputDir']);
70
 
 
71
 
        $this->_startDateString = $startDateString;
72
 
        $this->_endDateString   = $endDateString;
73
 
 
74
 
        // Also store as timestamps
75
 
        $this->_startTimestamp = toUnixTimestamp($startDateString);
76
 
        $this->_endTimestamp   = toUnixTimestamp($endDateString);
77
 
 
78
 
        // Get timestamps for frames in the key movie layer
79
 
        $this->_timestamps = $this->_getTimeStamps();
80
 
 
81
 
        $this->_numFrames  = sizeOf($this->_timestamps);
82
 
 
83
 
        if ($this->_numFrames == 0) {
84
 
                $this->_abort("No images available for the requested time range");
85
 
        }
86
 
 
87
 
        $this->_filename  = $this->_buildFilename($options['filename']);
88
 
        
89
 
        $this->_frameRate = $this->_determineOptimalFrameRate($options['frameRate']);
90
 
 
91
 
        $this->_setMovieDimensions();
92
 
        
93
 
        // Build movie frames
94
 
        $images = $this->_buildMovieFrames($options['watermarkOn']);
 
67
    public function __construct($publicId, $format="mp4")
 
68
    {
 
69
        $this->_db = new Database_ImgIndex();
 
70
        
 
71
        $id = alphaID($publicId, true, 5, HV_MOVIE_ID_PASS);
 
72
        $info = $this->_db->getMovieInformation($id, $format);
 
73
        
 
74
        $this->id           = $id;
 
75
        $this->publicId     = $publicId;
 
76
        $this->format       = $format;
 
77
        $this->reqStartDate = $info['reqStartDate'];
 
78
        $this->reqEndDate   = $info['reqEndDate'];
 
79
        $this->startDate    = $info['startDate'];
 
80
        $this->endDate      = $info['endDate'];
 
81
        $this->status       = $info['status'];
 
82
        $this->timestamp    = $info['timestamp'];
 
83
        $this->imageScale   = (float) $info['imageScale'];
 
84
        $this->frameRate    = (float) $info['frameRate'];
 
85
        $this->numFrames    = (int) $info['numFrames'];
 
86
        $this->maxFrames    = min((int) $info['maxFrames'], HV_MAX_MOVIE_FRAMES);
 
87
        $this->width        = (int) $info['width'];
 
88
        $this->height       = (int) $info['height'];
 
89
        $this->watermark    = (bool) $info['watermark'];
 
90
 
 
91
        // Data Layers
 
92
        $this->_layers = new Helper_HelioviewerLayers($info['dataSourceString']);
 
93
        
 
94
        // Regon of interest
 
95
        $this->_roi = Helper_RegionOfInterest::parsePolygonString($info['roi'], $info['imageScale']);
 
96
    }
 
97
    
 
98
    /**
 
99
     * Build the movie frames and movie
 
100
     */
 
101
    public function build()
 
102
    {
 
103
        // Check to make sure we have not already started processing the movie
 
104
        if ($this->status !== "QUEUED") {
 
105
            throw new Exception("The requested movie is either currently being built or has already been built");
 
106
        }
 
107
 
 
108
        $this->_db->markMovieAsProcessing($this->id, $this->format);
 
109
        
 
110
        try {
 
111
            $this->directory = $this->_buildDir();
 
112
    
 
113
            // If the movie frames have not been built create them
 
114
            if (!file_exists($this->directory . "frames")) {
 
115
                require_once 'src/Image/Composite/HelioviewerMovieFrame.php';
 
116
    
 
117
                $t1 = time();   
 
118
                         
 
119
                $this->_getTimeStamps();      // Get timestamps for frames in the key movie layer
 
120
                $this->_setMovieProperties(); // Sets the actual start and end dates, frame-rate, numFrames and dimensions
 
121
                $this->_buildMovieFrames($this->watermark); // Build movie frames
 
122
                
 
123
                $t2 = time();
 
124
                
 
125
                $this->_db->finishedBuildingMovieFrames($this->id, $t2 - $t1); // Update status and log time to build frames
 
126
            } else {
 
127
                $this->filename = $this->_buildFilename();
 
128
            }
 
129
        } catch (Exception $e) {
 
130
            $this->_abort("Error encountered during movie frame compilation");
 
131
        }
 
132
 
 
133
        $t3 = time();
95
134
 
96
135
        // Compile movie
97
 
        $this->_build($images);
98
 
    }
99
 
    
100
 
    /**
101
 
     * Returns an array of the timestamps for the key movie layer
102
 
     * 
103
 
     * For single layer movies, the number of frames will be either HV_MAX_MOVIE_FRAMES, or the number of
104
 
     * images available for the requested time range. For multi-layer movies, the number of frames included
105
 
     * may be reduced to ensure that the total number of SubFieldImages needed does not exceed HV_MAX_MOVIE_FRAMES
106
 
     */
107
 
    private function _getTimeStamps()
108
 
    {
109
 
        $layerCounts = array();
110
 
 
111
 
        // Determine the number of images that are available for the request duration for each layer
112
 
        foreach ($this->_layers->toArray() as $layer) {
113
 
            $n = $this->_db->getImageCount($this->_startDateString, $this->_endDateString, $layer['sourceId']);
114
 
            $layerCounts[$layer['sourceId']] = $n;
115
 
        }
116
 
 
117
 
        // Choose the maximum number of frames that can be generated without exceeded the server limits defined
118
 
        // by HV_MAX_MOVIE_FRAMES
119
 
        $numFrames       = 0;
120
 
        $imagesRemaining = HV_MAX_MOVIE_FRAMES;
121
 
        $layersRemaining = $this->_layers->length();
122
 
        
123
 
        // Sort counts from smallest to largest
124
 
        asort($layerCounts);
125
 
        
126
 
        // Determine number of frames to create
127
 
        foreach($layerCounts as $dataSource => $count) {
128
 
            $numFrames = min($count, ($imagesRemaining / $layersRemaining));
129
 
            $imagesRemaining -= $numFrames;
130
 
            $layersRemaining -= 1;
131
 
        }
132
 
        
133
 
        // Number of frames to use
134
 
        $numFrames = floor($numFrames);
135
 
 
136
 
        // Get the entire range of available images between the movie start and end time 
137
 
        $entireRange = $this->_db->getImageRange($this->_startDateString, $this->_endDateString, $dataSource);
138
 
        
139
 
        // Sub-sample range so that only $numFrames timestamps are returned
140
 
        $timestamps = array();
141
 
        for ($i = 0; $i < $numFrames; $i++) {
142
 
                $index = round($i * (sizeOf($entireRange) / $numFrames));
143
 
                array_push($timestamps, $entireRange[$index]['date']);
144
 
        }
145
 
        return $timestamps;        
146
 
    }
147
 
 
148
 
    /**
149
 
     * Create directories in cache used to store movies
150
 
     */
151
 
    private function _createCacheDirectories ($uuid, $dir)
152
 
    {
153
 
        // Regular movie requests use UUIDs and  event movies use the event identifiers
154
 
        if (!$dir) {
155
 
            if (!$uuid) {
156
 
                $uuid = uuid_create(UUID_TYPE_DEFAULT);
157
 
            }
158
 
            $dir = HV_CACHE_DIR . "/movies/" . $uuid;
159
 
        }
160
 
 
161
 
        if (!file_exists($dir)) {
162
 
            mkdir("$dir/frames", 0777, true);
163
 
            chmod("$dir/frames", 0777);
164
 
        }
165
 
 
166
 
        return $dir;
167
 
    }
168
 
 
169
 
    /**
170
 
     * Determines filename to use for storing movie
 
136
        try {
 
137
            $this->_encodeMovie();
 
138
        } catch (Exception $e) {
 
139
            $t4 = time();
 
140
            $this->_abort("Error encountered during video encoding.", $t4 - $t3);
 
141
        }
 
142
        
 
143
        // If all of the queued videos have been created remove frames
 
144
        if ($this->_db->getNumUnfinishedMovies($this->id) === 0) {
 
145
            $this->_cleanUp();
 
146
        }
 
147
    }
 
148
    
 
149
    /**
 
150
     * Returns information about the completed movie
 
151
     * 
 
152
     * @return array A list of movie properties and a URL to the finished movie
 
153
     */
 
154
    public function getCompletedMovieInformation() {
 
155
        return array(
 
156
            "frameRate"  => $this->frameRate,
 
157
            "numFrames"  => $this->numFrames,
 
158
            "startDate"  => $this->startDate,
 
159
            "status"     => $this->status,
 
160
            "endDate"    => $this->endDate,
 
161
            "width"      => $this->width,
 
162
            "height"     => $this->height,
 
163
            "thumbnails" => $this->getPreviewImages(),
 
164
            "url"        => $this->getURL()
 
165
        );
 
166
    }
 
167
    
 
168
    /**
 
169
     * Returns an array of filepaths to the movie's preview images
 
170
     */
 
171
    public function getPreviewImages()
 
172
    {
 
173
        $rootURL = str_replace(HV_CACHE_DIR, HV_CACHE_URL, $this->_buildDir());
 
174
        
 
175
        $images = array();
 
176
        
 
177
        foreach (array("icon", "small", "medium", "large", "full")  as $size) {
 
178
            $images[$size] = $rootURL . "preview-$size.png";
 
179
        }
 
180
        
 
181
        return $images;
 
182
    }
 
183
 
 
184
    /**
 
185
     * Returns the base filepath for movie without any file extension
 
186
     */
 
187
    public function getFilepath($highQuality=false)
 
188
    {
 
189
        return $this->_buildDir() . $this->_buildFilename($highQuality);
 
190
    }
 
191
    
 
192
    public function getDuration()
 
193
    {
 
194
        return $this->numFrames / $this->frameRate;
 
195
    }
 
196
    
 
197
    public function getURL()
 
198
    {
 
199
        return str_replace(HV_CACHE_DIR, HV_CACHE_URL, $this->_buildDir()) .
 
200
                $this->_buildFilename();
 
201
    }
 
202
    
 
203
    /**
 
204
     * Cancels movie request
 
205
     * 
 
206
     * @param string $msg Error message
 
207
     */
 
208
    private function _abort($msg, $procTime=0) {
 
209
        $this->_db->markMovieAsInvalid($this->id, $procTime);
 
210
        $this->_cleanUp();
 
211
        throw new Exception("Unable to create movie: " . $msg, 1);
 
212
    }
 
213
    
 
214
    /**
 
215
     * Determines the directory to store the movie in.
 
216
     * 
 
217
     * @return string Directory
 
218
     */
 
219
    private function _buildDir ()
 
220
    {
 
221
        $date = str_replace("-", "/", substr($this->timestamp, 0, 10));
 
222
        return sprintf("%s/movies/%s/%s/", HV_CACHE_DIR, $date, $this->publicId);
 
223
    }
 
224
 
 
225
    /**
 
226
     * Determines filename to use for the movie
 
227
     * 
 
228
     * @param string $extension Extension of the movie format to be created 
171
229
     *
172
 
     * @return string filename
 
230
     * @return string Movie filename
173
231
     */
174
 
    private function _buildFilename($filename) {
175
 
        if ($filename) {
176
 
            return $filename; 
177
 
        }
178
 
 
179
 
        $start = str_replace(array(":", "-", "T", "Z"), "_", $this->_startDateString);
180
 
        $end   = str_replace(array(":", "-", "T", "Z"), "_", $this->_endDateString);
181
 
 
182
 
        return sprintf("%s_%s_%s", $start, $end, $this->_layers->toString());
 
232
    private function _buildFilename($highQuality=false) {
 
233
        $start = str_replace(array(":", "-", " "), "_", $this->startDate);
 
234
        $end   = str_replace(array(":", "-", " "), "_", $this->endDate);
 
235
        
 
236
        $suffix = ($highQuality && $this->format == "mp4") ? "-hq" : "";
 
237
 
 
238
        return sprintf("%s_%s_%s%s.%s", $start, $end, $this->_layers->toString(), $suffix, $this->format);
183
239
    }
184
240
 
185
241
    /**
186
242
     * Takes in meta and layer information and creates movie frames from them.
 
243
     * 
 
244
     * TODO: Use middle frame instead last one...
 
245
     * TODO: Create standardized thumbnail sizes (e.g. thumbnail-med.png = 480x320, etc)
187
246
     *
188
247
     * @param {String} $tmpDir     the directory where the frames will be stored
189
248
     *
190
249
     * @return $images an array of built movie frames
191
250
     */
192
 
    private function _buildMovieFrames($watermarkOn)
 
251
    private function _buildMovieFrames($watermark)
193
252
    {
194
 
        $movieFrames  = array();
195
 
 
196
253
        $frameNum = 0;
197
254
 
198
255
        // Movie frame parameters
199
256
        $options = array(
200
 
            'compress'   => false,
201
 
            'interlace'  => false,
202
 
            'format'     => 'bmp',
203
 
            'watermarkOn'=> $watermarkOn,
204
 
            'outputDir'  => $this->_directory . "/frames"
 
257
            'database'  => $this->_db,
 
258
            'compress'  => false,
 
259
            'interlace' => false,
 
260
            'watermark' => $watermark
205
261
        );
 
262
        
 
263
        // Index of preview frame
 
264
        $previewIndex = floor($this->numFrames / 2);
206
265
 
207
266
        // Compile frames
208
267
        foreach ($this->_timestamps as $time) {
209
 
            $options = array_merge($options, array(
210
 
                'filename' => "frame" . $frameNum
211
 
            ));
 
268
            $filepath =  sprintf("%s/frames/frame%d.bmp", $this->directory, $frameNum);
212
269
 
213
270
            try {
214
 
                    $screenshot = new Image_Composite_HelioviewerCompositeImage($this->_layers, $time, $this->_roi, $options);
215
 
                    $filepath   = $screenshot->getFilepath();
 
271
                    $screenshot = new Image_Composite_HelioviewerMovieFrame($filepath, $this->_layers, $time, $this->_roi, $options);
 
272
                    
 
273
                    if ($frameNum == $previewIndex) {
 
274
                        $previewImage = $screenshot; // Make a copy of frame to be used for preview images
 
275
                    }
 
276
 
216
277
                    $frameNum++;
217
 
                    array_push($movieFrames, $filepath);
 
278
                    array_push($this->_frames, $filepath);
218
279
            } catch (Exception $e) {
219
 
                // Recover if failure occurs on a single frame
220
 
                $this->_numFrames--;
 
280
                $this->numFrames--; // Recover if failure occurs on a single frame
221
281
            }
222
282
        }
223
 
 
224
 
        // TODO 2011/02/14: Verify that this is still necessary
225
 
        // Copy the last frame so that it actually shows up in the movie for the same amount of time
226
 
        // as the rest of the frames.
227
 
        $lastImage = dirname($filepath) . "/frame" . $frameNum . "." . $options['format'];
228
 
 
229
 
        copy($filepath, $lastImage);
230
 
        array_push($movieFrames, $lastImage);
 
283
        $this->_createPreviewImages($previewImage);
 
284
    }
 
285
    
 
286
    /**
 
287
     * Remove movie frames and directory
 
288
     * 
 
289
     * @return void
 
290
     */
 
291
    private function _cleanUp()
 
292
    {
 
293
        $dir = $this->directory . "frames/";
231
294
        
 
295
        // Clean up movie frame images that are no longer needed
 
296
        if (file_exists($dir)) {
 
297
            foreach (glob("$dir*") as $image) {
 
298
                unlink($image);            
 
299
            }
 
300
            rmdir($dir);
 
301
        }   
 
302
    }
 
303
    
 
304
    /**
 
305
     * Creates preview images of several different sizes
 
306
     */
 
307
    private function _createPreviewImages(&$screenshot)
 
308
    {
232
309
        // Create preview image
233
 
        // TODO: Use middle frame instead last one...
234
 
        $imagickImage = $screenshot->getIMagickImage();
235
 
        $imagickImage->setImageCompression(IMagick::COMPRESSION_LZW);
236
 
        $imagickImage->setImageCompressionQuality(PNG_LOW_COMPRESSION);
237
 
        $imagickImage->setInterlaceScheme(IMagick::INTERLACE_PLANE);
238
 
        $imagickImage->writeImage($this->_directory . "/" . $this->_filename . ".png");
239
 
        $imagickImage->destroy();
240
 
 
241
 
        return $movieFrames;
 
310
        $preview = $screenshot->getIMagickImage();
 
311
        $preview->setImageCompression(IMagick::COMPRESSION_LZW);
 
312
        $preview->setImageCompressionQuality(PNG_LOW_COMPRESSION);
 
313
        $preview->setInterlaceScheme(IMagick::INTERLACE_PLANE);
 
314
        
 
315
        // Thumbnail sizes to create
 
316
        $sizes = array(
 
317
            "large"  => array(640, 480),
 
318
            "medium" => array(320, 240),
 
319
            "small"  => array(240, 180),
 
320
            "icon"   => array(64, 64)            
 
321
        );
 
322
        
 
323
        foreach ($sizes as $name=>$dimensions) {
 
324
            $thumb = $preview->clone();
 
325
            $thumb->thumbnailImage($dimensions[0], $dimensions[1], true);
 
326
            
 
327
            // Add black border to reach desired preview image sizes
 
328
            $borderWidth  = ceil(($dimensions[0] - $thumb->getImageWidth()) / 2);
 
329
            $borderHeight = ceil(($dimensions[1] - $thumb->getImageHeight()) / 2);
 
330
            
 
331
            $thumb->borderImage("black", $borderWidth, $borderHeight);
 
332
            $thumb->cropImage($dimensions[0], $dimensions[1], 0, 0);
 
333
            
 
334
            $thumb->writeImage($this->directory . "/preview-$name.png");
 
335
            $thumb->destroy();
 
336
        } 
 
337
        $preview->writeImage($this->directory . "/preview-full.png");  
 
338
        
 
339
        $preview->destroy();   
242
340
    }
243
341
 
244
342
    /**
250
348
    private function _determineOptimalFrameRate($requestedFrameRate)
251
349
    {
252
350
        // Subtract 1 because we added an extra frame to the end
253
 
        $frameRate = ($this->_numFrames - 1 ) / HV_DEFAULT_MOVIE_PLAYBACK_IN_SECONDS;
 
351
        $frameRate = ($this->numFrames - 1 ) / HV_DEFAULT_MOVIE_PLAYBACK_IN_SECONDS;
254
352
 
255
353
        // Take the smaller number in case the user specifies a larger frame rate than is practical.
256
354
        if ($requestedFrameRate) {
267
365
     * image to each timestamp for each layer. Then takes all layers belonging to one timestamp and makes a movie frame
268
366
     * out of it. When done with all movie frames, phpvideotoolkit is used to compile all the frames into a movie.
269
367
     *
270
 
     * @param array  $builtImages An array of built movie frames (in the form of HelioviewerCompositeImage objects)
271
 
     *
272
368
     * @return void
273
369
     */
274
 
    private function _build($builtImages)
 
370
    private function _encodeMovie()
275
371
    {
276
 
        $this->_frames = $builtImages;
277
 
 
 
372
        require_once 'src/Movie/FFMPEGEncoder.php';
 
373
        date_default_timezone_set('UTC');
 
374
        
 
375
        // Compute movie meta-data
 
376
        $layerString = $this->_layers->toHumanReadableString();
 
377
        
 
378
        // Date string
 
379
        if (substr($this->startDate, 0, 9) == substr($this->endDate, 0, 9)) {
 
380
            $dateString = sprintf("%s - %s UTC", $this->startDate, substr($this->endDate, 10));
 
381
        } else {
 
382
            $dateString = sprintf("%s - %s UTC", $this->startDate, $this->endDate);
 
383
        }
 
384
        
 
385
        // URL
 
386
        $url = "http://www.helioviewer.org/api/?action=downloadMovie&id={$this->id}&format={$this->format}";
 
387
        
 
388
        // Title
 
389
        $title = sprintf("%s (%s)", $layerString, $dateString);
 
390
        
 
391
        // Description
 
392
        $description = sprintf(
 
393
            "The Sun as seen through %s from %s.", 
 
394
            $layerString, str_replace("-", " to ", $dateString)
 
395
        );
 
396
        
 
397
        // Comment
 
398
        $comment = sprintf(
 
399
            "This movie was produced by Helioviewer.org on %s UTC. The original movie can be found at %s.", 
 
400
            date("F j, Y, g:i a"), $url
 
401
        );
 
402
        
 
403
        // MP4 filename
 
404
        $filename = str_replace("webm", "mp4", $this->filename);
 
405
        
278
406
        // Create and FFmpeg encoder instance
279
 
        $ffmpeg = new Movie_FFMPEGEncoder($this->_frameRate);
280
 
 
281
 
        // TODO 11/18/2010: add 'ipod' option to movie requests in place of the 'hqFormat' param
282
 
        $ipod = false;
283
 
 
284
 
        if ($ipod) {
285
 
            $ffmpeg->createIpodVideo($this->_directory, $this->_filename, "mp4", $this->_width, $this->_height);
286
 
        }
287
 
        
288
 
        // Create a high-quality H.264 video using an MPEG-4 (mp4) container format
289
 
        $ffmpeg->createVideo($this->_directory, $this->_filename . "-hq", "mp4", $this->_width, $this->_height, "ultrafast", 15);
290
 
 
291
 
        // Create a medium-quality H.264 video using an MPEG-4 (mp4) container format
292
 
        $ffmpeg->createVideo($this->_directory, $this->_filename, "mp4", $this->_width, $this->_height);
293
 
 
294
 
        //Create alternative container format options for medium-quality video (.flv)
295
 
        $ffmpeg->createAlternativeVideoFormat($this->_directory, $this->_filename, "mp4", "flv");
 
407
        $ffmpeg = new Movie_FFMPEGEncoder(
 
408
            $this->directory, $filename, $this->frameRate, $this->width, 
 
409
            $this->height, $title, $description, $comment
 
410
        );
 
411
        
 
412
        // Keep track of processing time for webm/mp4 encoding
 
413
        $t1 = time();
 
414
 
 
415
        // Create H.264 videos if they do not already exist
 
416
        if (!file_exists(realpath($this->directory . $filename))) {
 
417
            $ffmpeg->createVideo();
 
418
            $ffmpeg->createHQVideo();
 
419
            $ffmpeg->createFlashVideo();
 
420
            
 
421
            $t2 = time();
 
422
                    
 
423
            // Mark movie as completed
 
424
            $this->_db->markMovieAsFinished($this->id, "mp4", $t2 - $t1);
 
425
        }
 
426
 
 
427
        $t3 = time();
 
428
        
 
429
        //Create a Low-quality webm movie for in-browser use if requested
 
430
        if ($this->format == "webm") {
 
431
            $ffmpeg->setFormat("webm");
 
432
            $ffmpeg->createVideo();
 
433
            
 
434
            $t4 = time();
 
435
                    
 
436
            // Mark movie as completed
 
437
            $this->_db->markMovieAsFinished($this->id, "webm", $t4 - $t3);
 
438
        }
 
439
    }
 
440
    
 
441
    /**
 
442
     * Returns an array of the timestamps for the key movie layer
 
443
     * 
 
444
     * For single layer movies, the number of frames will be either HV_MAX_MOVIE_FRAMES, or the number of
 
445
     * images available for the requested time range. For multi-layer movies, the number of frames included
 
446
     * may be reduced to ensure that the total number of SubFieldImages needed does not exceed HV_MAX_MOVIE_FRAMES
 
447
     */
 
448
    private function _getTimeStamps()
 
449
    {
 
450
        $layerCounts = array();
 
451
 
 
452
        // Determine the number of images that are available for the request duration for each layer
 
453
        foreach ($this->_layers->toArray() as $layer) {
 
454
            $n = $this->_db->getImageCount($this->reqStartDate, $this->reqEndDate, $layer['sourceId']);
 
455
            $layerCounts[$layer['sourceId']] = $n;
 
456
        }
 
457
 
 
458
        // Choose the maximum number of frames that can be generated without exceeded the server limits defined
 
459
        // by HV_MAX_MOVIE_FRAMES
 
460
        $numFrames       = 0;
 
461
        $imagesRemaining = $this->maxFrames;
 
462
        $layersRemaining = $this->_layers->length();
 
463
        
 
464
        // Sort counts from smallest to largest
 
465
        asort($layerCounts);
 
466
        
 
467
        // Determine number of frames to create
 
468
        foreach($layerCounts as $dataSource => $count) {
 
469
            $numFrames = min($count, ($imagesRemaining / $layersRemaining));
 
470
            $imagesRemaining -= $numFrames;
 
471
            $layersRemaining -= 1;
 
472
        }
 
473
        
 
474
        // Number of frames to use
 
475
        $numFrames = floor($numFrames);
 
476
 
 
477
        // Get the entire range of available images between the movie start and end time 
 
478
        $entireRange = $this->_db->getImageRange($this->reqStartDate, $this->reqEndDate, $dataSource);
 
479
        
 
480
        // Sub-sample range so that only $numFrames timestamps are returned
 
481
        for ($i = 0; $i < $numFrames; $i++) {
 
482
            $index = round($i * (sizeOf($entireRange) / $numFrames));
 
483
            array_push($this->_timestamps, $entireRange[$index]['date']);
 
484
        }       
296
485
    }
297
486
 
298
487
    /**
301
490
     * @return void
302
491
     */
303
492
    private function _setMovieDimensions() {
304
 
        $this->_width  = round($this->_roi->getPixelWidth());
305
 
        $this->_height = round($this->_roi->getPixelHeight());
 
493
        $this->width  = round($this->_roi->getPixelWidth());
 
494
        $this->height = round($this->_roi->getPixelHeight());
306
495
 
307
496
        // Width and height must be divisible by 2 or ffmpeg will throw an error.
308
 
        if ($this->_width % 2 === 1) {
309
 
            $this->_width += 1;
 
497
        if ($this->width % 2 === 1) {
 
498
            $this->width += 1;
310
499
        }
311
500
        
312
 
        if ($this->_height % 2 === 1) {
313
 
            $this->_height += 1;
 
501
        if ($this->height % 2 === 1) {
 
502
            $this->height += 1;
314
503
        } 
315
504
    }
316
 
 
 
505
    
317
506
    /**
318
 
     * Cancels movie request
319
 
     * 
320
 
     * TODO 11/24/2010: Cleanup files
321
 
     * TODO 11/24/2010: Instead of using files to mark movie status, could instead use presence of 'frames' directory
322
 
     *                  and expected movies (frames directory is delete after movies are finished). In the longer run, 
323
 
     *                  movie status should be tracked in a database accessible to both Helioviewer and Helioqueuer.
 
507
     * Determines some of the movie details and saves them to the database record
324
508
     */
325
 
    private function _abort($msg) {
326
 
        touch($this->_directory . "/INVALID");
327
 
        throw new Exception("Unable to create movie: " . $msg, 1);
 
509
    private function _setMovieProperties()
 
510
    {
 
511
        // Store actual start and end dates that will be used for the movie
 
512
        $this->startDate = $this->_timestamps[0];
 
513
        $this->endDate   = $this->_timestamps[sizeOf($this->_timestamps) - 1];
 
514
 
 
515
        $this->filename = $this->_buildFilename();
 
516
        
 
517
        $this->numFrames = sizeOf($this->_timestamps);
 
518
 
 
519
        if ($this->numFrames == 0) {
 
520
            $this->_abort("No images available for the requested time range");
 
521
        }
 
522
 
 
523
        $this->frameRate = $this->_determineOptimalFrameRate($this->frameRate);
 
524
 
 
525
        $this->_setMovieDimensions();
 
526
 
 
527
        // Update movie entry in database with new details
 
528
        $this->_db->storeMovieProperties(
 
529
            $this->id, $this->startDate, $this->endDate, 
 
530
            $this->numFrames, $this->frameRate, $this->width, $this->height
 
531
        );
328
532
    }
329
533
 
330
534
    /**
362
566
    }
363
567
    
364
568
    /**
365
 
     * Returns the base filepath for movie without any file extension
366
 
     */
367
 
    public function getFilepath()
368
 
    {
369
 
        return $this->_directory . "/" . $this->_filename;
370
 
    }
371
 
    
372
 
    /**
373
 
     * Returns the Base URL to the most recently created movie (without a file extension)
374
 
     */
375
 
    public function getURL()
376
 
    {
377
 
        return str_replace(HV_ROOT_DIR, HV_WEB_ROOT_URL, $this->getFilepath());
378
 
    }
379
 
    
380
 
    /**
381
 
     * Returns the movie frame rate
382
 
     */
383
 
    public function getFrameRate()
384
 
    {
385
 
        return $this->_frameRate;
386
 
    }
387
 
    
388
 
    /**
389
 
     * Returns the number of frames in the movie
390
 
     */
391
 
    public function getNumFrames()
392
 
    {
393
 
        return $this->_numFrames;
394
 
    }
395
 
    
396
 
    public function getDuration()
397
 
    {
398
 
        return $this->_numFrames / $this->_frameRate;
399
 
    }
400
 
    
401
 
    /**
402
569
     * Returns HTML for a video player with the requested movie loaded
403
570
     */
404
571
    public function getMoviePlayerHTML()
405
572
    {
406
573
        $filepath = str_replace(HV_ROOT_DIR, "../", $this->getFilepath());
407
 
        $css      = "width: {$this->_width}px; height: {$this->_height}px;";
408
 
        $duration = $this->_numFrames / $this->_frameRate;
 
574
        $css      = "width: {$this->width}px; height: {$this->height}px;";
 
575
        $duration = $this->numFrames / $this->frameRate;
409
576
        ?>
410
577
<!DOCTYPE html> 
411
578
<html> 
412
579
<head> 
413
 
    <title>Helioviewer.org - <?php echo $this->_filename;?></title>            
 
580
    <title>Helioviewer.org - <?php echo $this -> filename;?></title>            
414
581
    <script type="text/javascript" src="http://html5.kaltura.org/js"></script> 
415
582
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js" type="text/javascript"></script>
416
583
</head> 
426
593
</div>
427
594
</body> 
428
595
</html> 
429
 
        <?php        
430
 
    }
431
 
    
432
 
    /**
433
 
     * Destructor
434
 
     * 
435
 
     * @return void
436
 
     */
437
 
    public function __destruct()
438
 
    {
439
 
        // Clean up movie frame images that are no longer needed
440
 
        foreach ($this->_frames as $image) {
441
 
            if (file_exists($image)) {
442
 
                unlink($image);
443
 
            }
444
 
        }
445
 
 
446
 
        rmdir($this->_directory . "/frames");
447
 
        touch($this->_directory . "/READY");
448
 
    }
449
 
}
 
596
        <?php
 
597
    }
 
598
    }
450
599
?>