Version: current

      1 <?php
      2
      3 class FLV extends File {
      4
      5     public static $allowed_file_types = array(
      6         'flv', 'avi', 'mov', 'mpeg', 'mpg', 'm4a'
      7     );
      8     private static $has_ffmpeg = null;
      9     private $allow_full_screen = true;
     10     private static $ffmpeg_root = "";
     11     private static $termination_code;
     12     public static $player_count = 0;
     13     public static $video_width = 840;
     14     public static $video_height = 525;
     15     public static $default_thumbnail_width = 640;
     16     public static $default_thumbnail_height = 480;
     17     public static $thumbnail_folder = "video_thumbnails";
     18     public static $log_file_path = "dataobject_manager/code/flv/ffmpeg_log.txt";
     19     public static $default_popup_width = 840;
     20     public static $default_popup_height = 525;
     21     public static $thumbnail_seconds = 10;
     22     public static $audio_sampling_rate = 22050;
     23     public static $audio_bit_rate = 32;
     24     public static $play_button_overlay = true;
     25     // .gif is also available to support the IE6 world, or specify your own.
     26     public static $default_video_icon_path = "dataobject_manager/code/flv/images/default_video.png";
     27
     28     public static function set_ffmpeg_root($path) {
     29         if (substr($path, -1) != "/")
     30             $path .= "/";
     31         self::$ffmpeg_root = $path;
     32     }
     33
     34     public static function has_ffmpeg() {
     35         // Cache this so we don't have to run a shell command every time.
     36         if (self::$has_ffmpeg !== null)
     37             return self::$has_ffmpeg;
     38
     39         $success = false;
     40         if (extension_loaded('ffmpeg'))
     41             $success = true;
     42         else {
     43             $output = self::ffmpeg("");
     44             if (self::$termination_code == 1)
     45                 $success = true;
     46         }
     47         self::$has_ffmpeg = $success;
     48         return self::$has_ffmpeg;
     49     }
     50
     51     public static function echo_ffmpeg_test() {
     52
     53         echo self::has_ffmpeg() ? "<span style='color:green'>FFMPEG is installed on your server and working properly. Code: " . self::$termination_code . "</span>" :
     54                 "<span class='color:red'>FFMPEG does not appear to be installed on your server. Code: " . self::$termination_code . "</span>";
     55     }
     56
     57     protected static function ffmpeg($args) {
     58         $descriptorspec = array(
     59             0 => array("pipe", "r"), // stdin is a pipe that the child will read from
     60             1 => array("pipe", "w"), // stdout is a pipe that the child will write to
     61             2 => array("pipe", "w") // stderr is a file to write to
     62         );
     63
     64         $pipes = array();
     65         $cmd = self::$ffmpeg_root . "ffmpeg " . $args;
     66         self::log_command($cmd);
     67         $process = proc_open($cmd, $descriptorspec, $pipes);
     68
     69         $output = "";
     70
     71         if (!is_resource($process))
     72             return false;
     73
     74         #close child's input immediately
     75         fclose($pipes[0]);
     76
     77         stream_set_blocking($pipes[1], false);
     78         stream_set_blocking($pipes[2], false);
     79
     80         $todo = array($pipes[1], $pipes[2]);
     81
     82         while (true) {
     83             $read = array();
     84             if (!feof($pipes[1]))
     85                 $read[] = $pipes[1];
     86             if (!feof($pipes[2]))
     87                 $read[] = $pipes[2];
     88
     89             if (!$read)
     90                 break;
     91
     92             $ready = stream_select($read, $write = NULL, $ex = NULL, 2);
     93
     94             if ($ready === false) {
     95                 break; #should never happen - something died
     96             }
     97
     98             foreach ($read as $r) {
     99                 $s = fread($r, 1024);
    100                 $output.= $s;
    101             }
    102         }
    103
    104         fclose($pipes[1]);
    105         fclose($pipes[2]);
    106
    107         self::$termination_code = proc_close($process);
    108         self::log_command($output);
    109         return $output;
    110     }
    111
    112     private static function log_command($cmd) {
    113         if (self::$log_file_path) {
    114             $log = Director::baseFolder() . "/" . self::$log_file_path;
    115             $f = @fopen($log, 'a');
    116             $entry = "[" . date('Y-m-d H:i:s') . "] " . $cmd . "\n";
    117             @fwrite($f, $entry);
    118             @fclose($f);
    119         }
    120     }
    121
    122     private function default_thumbnail() {
    123         $img = new Image_Cached(self::$default_video_icon_path);
    124         $img->ID = $this->ID;
    125         return $img;
    126     }
    127
    128     private function SWFLink() {
    129         return Director::absoluteURL('dataobject_manager/code/flv/shadowbox/libraries/mediaplayer/player.swf');
    130     }
    131
    132     private function AllowFullScreen() {
    133         return $this->allow_full_screen ? "true" : "false";
    134     }
    135
    136     private static function remove_file_extension($filename) {
    137         $ext = strrchr($filename, '.');
    138         if ($ext !== false)
    139             $filename = substr($filename, 0, -strlen($ext));
    140         return $filename;
    141     }
    142
    143     private static function clean_file($str) {
    144         $t = strtolower($str);
    145         $t = str_replace('&amp;', '-and-', $t);
    146         $t = str_replace('&', '-and-', $t);
    147         $t = preg_replace('![^A-Za-z0-9]+!', '-', $t);
    148         $t = preg_replace('!-+!', '-', $t);
    149         return $t;
    150     }
    151
    152     public function Icon() {
    153         return SAPPHIRE_DIR . "/images/app_icons/mov_32.gif";
    154     }
    155
    156     public function FLVPath() {
    157         return self::remove_file_extension($this->Filename) . ".flv";
    158     }
    159
    160     public function FLVLink() {
    161         return Director::absoluteURL($this->FLVPath());
    162     }
    163
    164     private function absoluteRawVideoLink() {
    165         return Director::baseFolder() . "/" . $this->Filename;
    166     }
    167
    168     private function absoluteFLVPath() {
    169         return Director::baseFolder() . "/" . $this->FLVPath();
    170     }
    171
    172     private function hasFLV() {
    173         return Director::fileExists($this->FLVPath());
    174     }
    175
    176     public function getThumbnail() {
    177         if ($img = DataObject::get_one("Image", "Title = 'flv_thumb_{$this->ID}'"))
    178             return Director::fileExists($img->Filename) ? $img : false;
    179         return false;
    180     }
    181
    182     private function createFLV() {
    183         $args = sprintf("-i %s -ar %d -ab %d -f flv %s",
    184                         $this->absoluteRawVideoLink(),
    185                         self::$audio_sampling_rate,
    186                         self::$audio_bit_rate,
    187                         $this->absoluteFLVPath()
    188         );
    189
    190         $output = self::ffmpeg($args);
    191     }
    192
    193     private function createThumbnail() {
    194         $img_title = "flv_thumb_" . $this->ID;
    195         if ($existing = DataObject::get("Image", "Title = '$img_title'")) {
    196             foreach ($existing as $file)
    197                 $file->delete();
    198         }
    199         $folder = Folder::findOrMake(self::$thumbnail_folder);
    200         $img_filename = self::clean_file(self::remove_file_extension($this->Title)) . ".jpg";
    201         $abs_thumb = Director::baseFolder() . "/" . $folder->Filename . $img_filename;
    202         $args = sprintf("-y -i %s -an -s %s -ss %d -an -r 1 -vframes 1 -y -vcodec mjpeg -f mjpeg %s",
    203                         str_replace(' ', '\ ', $this->absoluteFLVPath()),
    204                         self::$default_thumbnail_width . "x" . self::$default_thumbnail_height,
    205                         self::$thumbnail_seconds,
    206                         $abs_thumb
    207         );
    208         self::ffmpeg($args);
    209
    210         $img = new Image();
    211         $img->setField('ParentID', $folder->ID);
    212         $img->Filename = $folder->Filename . $img_filename;
    213         $img->Title = $img_title;
    214         $img->write();
    215     }
    216
    217     public function onBeforeWrite() {
    218         parent::onBeforeWrite();
    219         if (!$this->hasFLV())
    220             $this->createFLV();
    221         if (!$this->getThumbnail())
    222             $this->createThumbnail();
    223     }
    224
    225     public function Player($width = null, $height = null) {
    226         if ($width === null)
    227             $width = self::$video_width;
    228         if ($height === null)
    229             $height = self::$video_height;
    230         $image = ($thumb = $this->VideoThumbnail()) ? $thumb->CroppedImage($width, $height)->URL : "";
    231         self::$player_count++;
    232         Requirements::javascript('dataobject_manager/code/flv/swfobject.js');
    233         Requirements::customScript(sprintf(
    234                                 "swfobject.embedSWF('%s','player-%s','%d','%d','9.0.0','expressInstall.swf',{file : '%s',image : '%s'},{allowscriptaccess : 'true', allowfullscreen : '%s'})",
    235                                 $this->SWFLink(),
    236                                 self::$player_count,
    237                                 $width,
    238                                 $height,
    239                                 $this->FLVLink(),
    240                                 $image,
    241                                 $this->AllowFullScreen()
    242                         )
    243         );
    244         return "<div id='player-" . self::$player_count . "'>Loading...</div>";
    245     }
    246
    247     public function forTemplate() {
    248         return $this->Player();
    249     }
    250
    251     public function VideoThumbnail() {
    252         if (self::has_ffmpeg() && !$img = $this->getThumbnail())
    253             $this->createThumbnail();
    254         $img = $this->getThumbnail();
    255         return $img ? $img : $this->default_thumbnail();
    256     }
    257
    258     /**
    259      * SSViewer doesn't accept more than two arguments for template
    260      * functions. Here's a hack. If an arg is, e.g. 200x400 it will
    261      * split that into width/height for thumb for first arg, and popup
    262      * for second arg.
    263      *
    264      * Examples:
    265      * $VideoPopup(450,200) : Returns a video popup with thumbnail
    266      *                        450 width, 200 height. Popup is default dimensions
    267      *
    268      * $VideoPopup(450x200,800x600) : Returns a video popup with thumbnail
    269      *                                450 width, 200 height. Popup is 800 width, 600 height.
    270      *
    271      * $VideoPopup(450x200) : Same as first example.
    272      *
    273      */
    274     public function VideoPopup($arg1 = null, $arg2 = null) {
    275         $popup_width = null;
    276         $popup_height = null;
    277         if ($arg1 !== null && stristr($arg1, "x"))
    278             list($thumb_width, $thumb_height) = explode("x", $arg1);
    279         else
    280             $thumb_width = $arg1;
    281
    282         if ($arg2 !== null && stristr($arg2, "x"))
    283             list($popup_width, $popup_height) = explode("x", $arg2);
    284         else
    285             $thumb_height = $arg2;
    286
    287         if ($popup_width === null)
    288             $popup_width = self::$default_popup_width;
    289         if ($popup_height === null)
    290             $popup_height = self::$default_popup_height;
    291
    292         return $this->customise(array(
    293             'PopupWidth' => $popup_width,
    294             'PopupHeight' => $popup_height,
    295             'ThumbWidth' => $thumb_width,
    296             'ThumbHeight' => $thumb_height,
    297             'Title' => $this->Title,
    298             'Link' => $this->FLVLink(),
    299             'Thumbnail' => $this->VideoThumbnail()->CroppedImage($thumb_width, $thumb_height),
    300             'PlayButton' => self::$play_button_overlay
    301         ))->renderWith(array('FLVpopup'));
    302     }
    303
    304 }
    305
    306 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -