~keith-hughitt/helioviewer.org/2.0

« back to all changes in this revision

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

  • Committer: Keith Hughitt
  • Date: 2010-03-24 11:14:04 UTC
  • Revision ID: hughitt1@io-20100324111404-tjat3xqy09nqvwik
Helioviewer.orgĀ 2.0.0

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
 * PHP version 5
 
7
 *
 
8
 * @category Movie
 
9
 * @package  Helioviewer
 
10
 * @author   Jaclyn Beck <jabeck@nmu.edu>
 
11
 * @license  http://www.mozilla.org/MPL/MPL-1.1.html Mozilla Public License 1.1
 
12
 * @link     http://launchpad.net/helioviewer.org
 
13
 */
 
14
require_once 'HelioviewerMovieFrame.php';
 
15
require_once 'src/Database/DbConnection.php';
 
16
require_once 'lib/phpvideotoolkit/config.php';
 
17
require_once 'lib/phpvideotoolkit/phpvideotoolkit.php5.php';
 
18
/**
 
19
 * Represents a static (e.g. ogv/mp4) movie generated by Helioviewer
 
20
 *
 
21
 * Note: For movies, it is easiest to work with Unix timestamps since that is what is returned
 
22
 *       from the database. To get from a javascript Date object to a Unix timestamp, simply
 
23
 *       use "date.getTime() * 1000." (getTime returns the number of miliseconds)
 
24
 *
 
25
 * @category Movie
 
26
 * @package  Helioviewer
 
27
 * @author   Jaclyn Beck <jabeck@nmu.edu>
 
28
 * @license  http://www.mozilla.org/MPL/MPL-1.1.html Mozilla Public License 1.1
 
29
 * @link     http://launchpad.net/helioviewer.org
 
30
 */
 
31
class Movie_HelioviewerMovie
 
32
{
 
33
    private $_images = array ();
 
34
    private $_imageSize;
 
35
    private $_maxFrames;
 
36
    private $_startTime;
 
37
    private $_endTime;
 
38
    private $_timeStep;
 
39
    private $_db;
 
40
    private $_baseScale = 2.63;
 
41
    private $_baseZoom = 10;
 
42
    private $_tileSize = 512;
 
43
    private $_filetype = "flv";
 
44
    private $_highQualityLevel = 100;
 
45
    private $_watermarkOptions = "-x 720 -y 965 ";
 
46
 
 
47
    /**
 
48
     * HelioviewerMovie Constructor
 
49
     *
 
50
     * @param array  $layers    Layers to use for movie generation. Each layer is a string of the form:
 
51
     *                          OBS_INST_DET_MEAS,xstart,xsize,ystart,ysize,hcOffsetx,hcOffsety,opacity
 
52
     * @param int    $startTime Requested movie start time (unix timestamp)
 
53
     * @param int    $zoomLevel Zoom-level for which the movie was requested at
 
54
     * @param int    $numFrames Number of frames to include
 
55
     * @param int    $frameRate Number of frames per second
 
56
     * @param string $hqFormat  Format to use for high-quality version of the movie
 
57
     * @param array  $options   An array with ["edges"] => true/false, ["sharpen"] => true/false
 
58
     * @param int    $timeStep  Desired timestep between movie frames in seconds. Default is 86400 seconds, or 1 day.
 
59
     * @param array  $imageSize Width and height of each movie frame
 
60
     * @param string $filename  Desired filename for the movie
 
61
     * @param int    $quality   Movie quality
 
62
     */
 
63
    public function __construct(
 
64
        $layers, $startTime, $zoomLevel, $numFrames, $frameRate, $hqFormat,
 
65
        $options, $timeStep, $imageSize, $filename, $quality
 
66
    ) {
 
67
        // date_default_timezone_set('UTC');
 
68
        // $layers is an array of layer information arrays, identified by their layer names.
 
69
        // Each layer information array has values for "name", "xRange", "yRange", "hcOffset", and "opacityValue"
 
70
        $this->layers = $layers;
 
71
 
 
72
        // working directory
 
73
        $this->tmpdir = substr(getcwd(), 0, -3) + "tmp";
 
74
 
 
75
        // _startTime is a Unix timestamp in seconds.
 
76
        $this->_startTime = $startTime;
 
77
        $this->zoomLevel  = $zoomLevel;
 
78
        $this->numFrames  = $numFrames;
 
79
        $this->frameRate  = $frameRate;
 
80
        $this->quality    = $quality;
 
81
        $this->options    = $options;
 
82
 
 
83
        // _timeStep is in seconds
 
84
        $this->_timeStep  = $timeStep;
 
85
        $this->_imageSize = $imageSize;
 
86
        $this->filename   = $filename;
 
87
 
 
88
        $this->_endTime = $startTime + ($numFrames * $timeStep);
 
89
 
 
90
        $this->padDimensions = $this->_setAspectRatios();
 
91
        $this->highQualityFiletype = $hqFormat;
 
92
        $this->_db = new Database_DbConnection();
 
93
    }
 
94
 
 
95
    /**
 
96
     * TODO: implement
 
97
     *
 
98
     * @return void
 
99
     */
 
100
    public function toMovie()
 
101
    {
 
102
 
 
103
    }
 
104
 
 
105
    /**
 
106
     * TODO: implement
 
107
     *
 
108
     * @return void
 
109
     */
 
110
    public function toArchive()
 
111
    {
 
112
 
 
113
    }
 
114
 
 
115
    /**
 
116
     * TODO: implement
 
117
     *
 
118
     * @return void
 
119
     */
 
120
    public function getNumFrames()
 
121
    {
 
122
 
 
123
    }
 
124
 
 
125
    /**
 
126
     * Builds the requested movie
 
127
     *
 
128
     * Makes a temporary directory to store frames in, calculates a timestamp for every frame, gets the closest
 
129
     * image to each timestamp for each layer. Then takes all layers belonging to one timestamp and makes a movie frame
 
130
     * out of it. When done with all movie frames, phpvideotoolkit is used to compile all the frames into a movie.
 
131
     *
 
132
     * @return void
 
133
     */
 
134
    public function buildMovie()
 
135
    {
 
136
        // Make a temporary directory to store the movie in.
 
137
        $now       = time();
 
138
        $movieName = "Helioviewer-Movie-" . $this->filename;
 
139
        $tmpdir    = HV_TMP_DIR . "/$now/";
 
140
        $tmpurl    = HV_TMP_ROOT_URL . "/$now/$movieName." . $this->_filetype;
 
141
        mkdir($tmpdir);
 
142
        chmod($tmpdir, 0777);
 
143
 
 
144
        // Build an array with all timestamps needed when requesting images
 
145
        $timeStamps = array();
 
146
 
 
147
        // Calculates unix time stamps, successively increasing by the time step
 
148
        // (default step is 86400 seconds, or 1 day)
 
149
        for ($time = $this->_startTime; $time < $this->_endTime; $time += $this->_timeStep) {
 
150
            array_push($timeStamps, $time);
 
151
        }
 
152
 
 
153
        // Array that holds $closestImage array for each layer
 
154
        $layerImages = array();
 
155
 
 
156
        // Array to hold timestamps corresponding to each image, and each image's uri
 
157
        $closestImage = array();
 
158
 
 
159
        foreach ($this->layers as $layer) {
 
160
            // $layerInfo will have values Array
 
161
            // ("obs_inst_det_meas", xStart, xSize, yStart, ySize, offsetx, offsety, opacity)
 
162
            $layerInfo = explode(",", $layer);
 
163
 
 
164
            // name is now: 'obs_inst_det_meas'
 
165
            $name = $layerInfo[0];
 
166
 
 
167
            // closestImage is an associative array the size of numFrames with each entry having:
 
168
            // Array('timestamp', 'unix_timestamp', 'timediff', 'timediffAbs', 'uri', 'opacityGrp')
 
169
            $closestImage = $this->_getImageTimestamps($name, $timeStamps);
 
170
 
 
171
            // layerImages is an associative array the size of the number of layers. An example entry would be:
 
172
            // layerImages['SOH_EIT_EIT_304'] = closestImage array.
 
173
            // So each entry has an array of the closest images to each timestamp.
 
174
            $layerImages[$name] = $closestImage;
 
175
        }
 
176
 
 
177
        // For each frame, make a composite image of all layers at that timestamp
 
178
        for ($frameNum = 0; $frameNum < $this->numFrames; $frameNum++) {
 
179
            // images array holds one image from each layer (the closest images to a specific timestamp)
 
180
            $images = array();
 
181
            $realTimestamps = array();
 
182
 
 
183
            foreach ($this->layers as $layer) {
 
184
                $layerInfo = explode(",", $layer);
 
185
 
 
186
                // name is 'SOH_EIT_EIT_304'
 
187
                $name = $layerInfo[0];
 
188
 
 
189
                // Chop the name off the array but keep the rest of the information.
 
190
                // ranges is an array: [xStart, xSize, yStart, ySize, offsetX, offsetY, opacity]
 
191
                $ranges = array_slice($layerInfo, 1);
 
192
 
 
193
                $closestImage = $layerImages[$name][$frameNum];
 
194
 
 
195
                // $image is now: "uri,xStart,xSize,yStart,ySize,opacity,opacityGrp"
 
196
                $image =  $closestImage['uri'] . "," . implode(",", $ranges) . "," .$closestImage['opacityGrp'];
 
197
                $images[$name] = $image;
 
198
                $realTimestamps[$name] = $closestImage['timestamp'];
 
199
            }
 
200
 
 
201
            // All frames will be put in cache/movies/$now
 
202
            $movieFrame = new Movie_HelioviewerMovieFrame(
 
203
                $this->zoomLevel, $this->options, $images, $frameNum, $now,
 
204
                $this->_imageSize, $realTimestamps, $this->quality
 
205
            );
 
206
            $frameFile = $movieFrame->getComposite();
 
207
 
 
208
            array_push($this->_images, $frameFile);
 
209
        }
 
210
 
 
211
        // Pad to a 16:9 aspect ratio by adding a black border around the image.
 
212
        // This is set up so that width CAN be padded if it's uncommented. Currently it is not padded.
 
213
        foreach ($this->_images as $image) {
 
214
            //$imgWidth = $this->_imageSize["width"];
 
215
            //$width     = $this->padDimensions["width"];
 
216
            //$widthDiff = ($width - $imgWidth) / 2;
 
217
 
 
218
            $imgHeight = $this->_imageSize["height"];
 
219
            $height = $this->padDimensions["height"];
 
220
            $heightDiff = ($height - $imgHeight) / 2;
 
221
 
 
222
            if (/*$widthDiff > 0 || */ $heightDiff > 0) {
 
223
                $padCmd = ' && convert -bordercolor black -border 0x' . $heightDiff . " " . $image . " " . $image;
 
224
                exec(HV_PATH_CMD . escapeshellcmd($padCmd));
 
225
            }
 
226
        }
 
227
 
 
228
        // Use phpvideotoolkit to compile them
 
229
        $toolkit = new PHPVideoToolkit($tmpdir);
 
230
 
 
231
        // compile the image to the tmp dir
 
232
        $ok = $toolkit->prepareImagesForConversionToVideo($this->_images, $this->frameRate);
 
233
 
 
234
        if (!$ok) {
 
235
            // if there was an error then get it
 
236
            logErrorMsg("PHPVideoToolkit: {$toolkit->getLastError()}");
 
237
        }
 
238
 
 
239
        $toolkit->setVideoOutputDimensions($this->_imageSize['width'], $this->_imageSize['height']);
 
240
 
 
241
        // set the output parameters (Flash video)
 
242
        $output_filename = "$movieName." . $this->_filetype;
 
243
        $ok = $toolkit->setOutput($tmpdir, $output_filename, PHPVideoToolkit::OVERWRITE_EXISTING);
 
244
 
 
245
        if (!$ok) {
 
246
            //         if there was an error then get it
 
247
            logErrorMsg("PHPVideoToolkit: {$toolkit->getLastError()}");
 
248
        }
 
249
 
 
250
        //     execute the ffmpeg command
 
251
        $movie = $toolkit->execute(false, true);
 
252
 
 
253
        // check the return value in-case of error
 
254
        if ($movie !== PHPVideoToolkit::RESULT_OK) {
 
255
            // if there was an error then get it
 
256
            logErrorMsg("PHPVideoToolkit: {$toolkit->getLastError()}");
 
257
        }
 
258
 
 
259
        // Create a high-quality version as well
 
260
        $hq_filename = "$movieName." . $this->highQualityFiletype;
 
261
        $toolkit->setConstantQuality($this->_highQualityLevel);
 
262
 
 
263
        // Use ASF for Windows
 
264
        if ($this->highQualityFiletype == "avi") {
 
265
            $toolkit->setFormat(PHPVideoToolkit::FORMAT_ASF);
 
266
        }
 
267
 
 
268
        // Use MPEG-4 for Mac
 
269
        if ($this->highQualityFiletype == "mov") {
 
270
            $toolkit->setVideoCodec(PHPVideoToolkit::FORMAT_MPEG4);
 
271
        }
 
272
 
 
273
        // Add a watermark
 
274
        //$watermark = HV_ROOT_DIR . "/images/logos/watermark_small_gs.png";
 
275
        //$toolkit->addWatermark($watermark, PHPVIDEOTOOLKIT_FFMPEG_IMLIB2_VHOOK, $this->_watermarkOptions);
 
276
 
 
277
        $ok = $toolkit->setOutput($tmpdir, $hq_filename, PHPVideoToolkit::OVERWRITE_EXISTING);
 
278
 
 
279
        if (!$ok) {
 
280
            // if there was an error then get it
 
281
            logErrorMsg("PHPVideoToolkit: {$toolkit->getLastError()}");
 
282
        }
 
283
 
 
284
        // execute the ffmpeg command
 
285
        $mp4 = $toolkit->execute(false, true);
 
286
 
 
287
        if ($mp4 !== PHPVideoToolkit::RESULT_OK) {
 
288
            //         if there was an error then get it
 
289
            logErrorMsg("PHPVideoToolkit: {$toolkit->getLastError()}");
 
290
        }
 
291
 
 
292
        // Clean up png/tif images that are no longer needed
 
293
        foreach ($this->_images as $image) {
 
294
            unlink($image);
 
295
        }
 
296
 
 
297
        // $this->showMovie($tmpurl, 512, 512);
 
298
 
 
299
        header('Content-type: application/json');
 
300
        echo json_encode($tmpurl);
 
301
    }
 
302
 
 
303
    /**
 
304
     * Adds black border to movie frames if neccessary to guarantee a 16:9 aspect ratio
 
305
     *
 
306
     * Checks the ratio of width to height and adjusts each dimension so that the
 
307
     * ratio is 16:9. The movie will be padded with a black background in JP2Image.php
 
308
     * using the new width and height.
 
309
     *
 
310
     * @return array Width and Height of padded movie frames
 
311
     */
 
312
    private function _setAspectRatios()
 
313
    {
 
314
        $width  = $this->_imageSize["width"];
 
315
        $height = $this->_imageSize["height"];
 
316
 
 
317
        $ratio = $width / $height;
 
318
 
 
319
        // Commented out because padding the width looks funny.
 
320
        /*
 
321
        // If width needs to be adjusted but height is fine
 
322
        if ($ratio < 16/9) {
 
323
            $adjust = (16/9) * $height / $width;
 
324
            $width *= $adjust;
 
325
        }
 
326
        */
 
327
        // Adjust height if necessary
 
328
        if ($ratio > 16/9) {
 
329
            $adjust = (9/16) * $width / $height;
 
330
            $height *= $adjust;
 
331
        }
 
332
 
 
333
        $dimensions = array("width" => $width, "height" => $height);
 
334
        return $dimensions;
 
335
    }
 
336
 
 
337
    /**
 
338
     * Find closest times for each frame of the movie for a given layer
 
339
     *
 
340
     * Queries the database to find the exact timestamps for images nearest each time in $timeStamps.
 
341
     * Returns an array the size of numFrames that has:
 
342
     *     'timestamp', 'unix_timestamp', 'timediff', 'timediffAbs', 'uri', and 'opacityGrp'
 
343
     * for each image.
 
344
     *
 
345
     * @param string $name       JP2 filename
 
346
     * @param array  $timeStamps Array containing the requested timeStamps to match against
 
347
     *
 
348
     * @return array Matched times
 
349
     */
 
350
    private function _getImageTimestamps($name, $timeStamps) //($obs, $inst, $det, $meas, $timeStamps)
 
351
    {
 
352
        $resultArray = array ();
 
353
 
 
354
        // Go through the array and find the closest image in the database to the given timeStamp
 
355
        if ($timeStamps) {
 
356
            foreach ($timeStamps as $time) {
 
357
                // sprintf takes too long, especially when it is called 40+ times.
 
358
                $sql = "SELECT
 
359
                            DISTINCT timestamp,
 
360
                            UNIX_TIMESTAMP(timestamp) AS unix_timestamp,
 
361
                            UNIX_TIMESTAMP(timestamp) - $time AS timediff,
 
362
                            ABS(UNIX_TIMESTAMP(timestamp) - $time) AS timediffAbs,
 
363
                            uri,
 
364
                            opacityGrp
 
365
                        FROM
 
366
                            image
 
367
                        WHERE
 
368
                            uri
 
369
                        LIKE
 
370
                            '%_%_%_%_" . mysqli_real_escape_string($this->_db->link, $name) . ".jp2'
 
371
                        ORDER BY
 
372
                            timediffAbs
 
373
                        LIMIT 0,1";
 
374
                try {
 
375
                    $result = $this->_db->query($sql);
 
376
                    $row = mysqli_fetch_array($result, MYSQL_ASSOC);
 
377
                    if (!$row) {
 
378
                        throw new Exception("Could not find the requested image.");
 
379
                    }
 
380
                }
 
381
                catch (Exception $e) {
 
382
                    logErrorMsg($e->getMessage);
 
383
                }
 
384
 
 
385
                array_push($resultArray, $row);
 
386
            }
 
387
        }
 
388
 
 
389
        return $resultArray;
 
390
    }
 
391
 
 
392
    /**
 
393
     * Displays movie in a Flash player along with a link to the high-quality version
 
394
     *
 
395
     * @param string $url    The URL for the movie to be displayed
 
396
     * @param int    $width  Movie width
 
397
     * @param int    $height Movie Height
 
398
     *
 
399
     * @return void
 
400
     */
 
401
    public function showMovie($url, $width, $height)
 
402
    {
 
403
        ?>
 
404
        <!-- MC Media Player -->
 
405
        <script type="text/javascript">
 
406
            playerFile = "http://www.mcmediaplayer.com/public/mcmp_0.8.swf";
 
407
            fpFileURL = "<?php print $url?>";
 
408
            playerSize = "<?php print $width . 'x' . $height?>";
 
409
        </script>
 
410
        <script type="text/javascript" src="http://www.mcmediaplayer.com/public/mcmp_0.8.js">
 
411
        </script>
 
412
        <!-- / MC Media Player -->
 
413
        <?php
 
414
    }
 
415
}
 
416
?>