├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── public └── .gitkeep ├── src ├── Rafasamp │ └── Sonus │ │ ├── Facade.php │ │ ├── Helpers.php │ │ ├── Sonus.php │ │ └── SonusServiceProvider.php ├── config │ ├── .gitkeep │ └── config.php ├── controllers │ └── .gitkeep ├── lang │ └── .gitkeep ├── migrations │ └── .gitkeep └── views │ └── .gitkeep └── tests ├── .gitkeep ├── HelpersTest.php └── SonusTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | matrix: 10 | allow_failures: 11 | - php: 5.6 12 | - php: hhvm 13 | 14 | before_script: 15 | - curl -s http://getcomposer.org/installer | php 16 | - php composer.phar install --dev 17 | 18 | script: phpunit 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Rafael Sampaio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Status 2 | I regret to inform that I will not be updating Sonus any further. Unfortunately life has got in the way and I cannot dedicate the time for this project. There is a fork by closca [here](https://github.com/closca/sonus) that supports Laravel 5. I will keep the Github page alive in case anyone needs to reference it. 3 | 4 | Thank you to everyone who used this little project of mine! 5 | 6 | # Sonus (Laravel 4 Package) 7 | [![Latest Stable Version](https://poser.pugx.org/rafasamp/sonus/v/stable.png)](https://packagist.org/packages/rafasamp/sonus) 8 | [![Build Status](https://travis-ci.org/rafasamp/sonus.png?branch=master)](https://travis-ci.org/rafasamp/sonus) 9 | [![Total Downloads](https://poser.pugx.org/rafasamp/sonus/downloads.png)](https://packagist.org/packages/rafasamp/sonus) 10 | [![ProjectStatus](http://stillmaintained.com/rafasamp/sonus.png)](http://stillmaintained.com/rafasamp/sonus) 11 | [![License](https://poser.pugx.org/rafasamp/sonus/license.png)](https://packagist.org/packages/rafasamp/sonus) 12 | 13 | Sonus is a tool designed to leverage the power of **Laravel 4** and **ffmpeg** to perform tasks such as: 14 | 15 | * Audio/Video conversion 16 | * Video thumbnail generation 17 | * Metadata manipulation 18 | 19 | ## Quick Start 20 | 21 | ### Setup 22 | 23 | Update your `composer.json` file and add the following under the `require` key 24 | 25 | "rafasamp/sonus": "dev-master" 26 | 27 | Run the composer update command: 28 | 29 | $ composer update 30 | 31 | In your `config/app.php` add `'Rafasamp\Sonus\SonusServiceProvider'` to the end of the `$providers` array 32 | 33 | 'providers' => array( 34 | 35 | 'Illuminate\Foundation\Providers\ArtisanServiceProvider', 36 | 'Illuminate\Auth\AuthServiceProvider', 37 | ... 38 | 'Rafasamp\Sonus\SonusServiceProvider', 39 | 40 | ), 41 | 42 | Still under `config/app.php` add `'Sonus' => 'Rafasamp\Sonus\Facade'` to the `$aliases` array 43 | 44 | 'aliases' => array( 45 | 46 | 'App' => 'Illuminate\Support\Facades\App', 47 | 'Artisan' => 'Illuminate\Support\Facades\Artisan', 48 | ... 49 | 'Sonus' => 'Rafasamp\Sonus\Facade', 50 | 51 | ), 52 | 53 | Run the `artisan` command below to publish the configuration file 54 | 55 | $ php artisan config:publish rafasamp/sonus 56 | 57 | Navigate to `app/config/packages/Rafasamp/Sonus/config.php` and update all four parameters 58 | 59 | ### Examples 60 | 61 | Here is a simple example of a file being converted from FLAC to AAC: 62 | 63 | Sonus::convert()->input('foo.flac')->bitrate(128)->output('bar.aac')->go(); 64 | 65 | Sonus can also convert video files: 66 | 67 | Sonus::convert()->input('foo.avi')->bitrate(300, 'video')->output('bar.flv')->go(); 68 | 69 | Sonus can also return media information as an array or json 70 | 71 | Sonus::getMediaInfo('foo.mov'); 72 | 73 | Sonus can also easily generate smart movie thumbnails like this 74 | 75 | Sonus::getThumbnails('foo.mp4', 'foo-thumb' 5); // Yields 5 thumbnails 76 | 77 | Although Sonus contains several preset parameters, you can also pass your own 78 | 79 | Sonus::convert()->input('foo.flac')->output('bar.mp3')->go('-b:a 64k -ac 1'); 80 | 81 | ### Tracking progress 82 | 83 | Make sure the `progress` and `tmp_dir` options are set correctly in the config.php file 84 | 85 | 'progress' => true, 86 | ... 87 | 'tmp_dir' => '/Applications/ffmpeg/tmp/' 88 | 89 | Pass the progress method when initiating a conversion 90 | 91 | Sonus::convert()->input('foo.avi')->output('bar.mp3')->progress('uniqueid')->go(); 92 | 93 | Now you can write a controller action to return the progress for the job id you passed and call it using any flavor of JavaScript you like 94 | 95 | public function getJobProgress($id) 96 | { 97 | return Sonus::getProgress('uniqueid'); 98 | } 99 | 100 | ### Security and Compatibility 101 | 102 | Sonus uses PHP's [shell_exec](http://us3.php.net/shell_exec) function to perform ffmpeg and ffprobe commands. This command is disabled if you are running PHP 5.3 or below and [safe mode](http://us3.php.net/manual/en/features.safe-mode.php) is enabled. 103 | 104 | Please make sure that ffmpeg and ffprobe are at least the following versions: 105 | 106 | * ffmpeg: 2.1.* 107 | * ffprobe: 2.0.* 108 | 109 | Also, remember that filepaths must be relative to the location of FFMPEG on your system. To ensure compatibility, it is good practice to pass the full path of the input and output files. Here's an example working in Laravel: 110 | 111 | $file_in = Input::file('audio')->getRealPath(); 112 | $file_out = '\path\to\my\file.mp3'; 113 | Sonus::convert()->input($file_in)->output($file_out)->go(); 114 | 115 | Lastly, Sonus will only convert to formats which ffmpeg supports. To check if your version of ffmpeg is configured to encode or decode a specific format you can run the following commands using `php artisan tinker` 116 | 117 | var_dump(Sonus::canEncode('mp3')); 118 | var_dump(Sonus::canDecode('mp3')); 119 | 120 | To get a list of all supported formats you can run 121 | 122 | var_dump(Sonus::getSupportedFormats()); 123 | 124 | ## Troubleshooting 125 | 126 | Please make sure the following statements are true before opening an issue: 127 | 128 | 1) I am able to access FFMPEG on terminal using the same path I defined in the Sonus configuration file 129 | 130 | 2) I have checked the error logs for the webserver and found no FFMPEG output messages 131 | 132 | Usually all concerns are taken care of by following these two steps. If you still find yourself having issues you can always open a trouble ticket. 133 | 134 | ## Planned features 135 | 136 | * Support for [filters](http://ffmpeg.mplayerhq.hu/ffmpeg-filters.html) 137 | * Setting metadata 138 | * Return meaningful error codes on exceptions 139 | 140 | ## License 141 | 142 | Sonus is free software distributed under the terms of the MIT license. 143 | 144 | ## Aditional information 145 | 146 | Any questions, feel free to contact me. 147 | 148 | Any issues, please [report here](https://github.com/rafasamp/sonus/issues) 149 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rafasamp/sonus", 3 | "description": "A laravel audio and video conversion, thumbnail generator and metadata editor package powered by ffmpeg", 4 | "keywords": ["laravel", "ffmpeg", "ffprobe", "converter"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Rafael Sampaio", 9 | "email": "rafaelsampaio@live.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.4.0", 14 | "illuminate/support": "4.2.*" 15 | }, 16 | "require-dev": { 17 | "phpunit/phpunit": "4.1.*" 18 | }, 19 | "autoload": { 20 | "classmap": [ 21 | "src/migrations" 22 | ], 23 | "psr-0": { 24 | "Rafasamp\\Sonus\\": "src/" 25 | } 26 | }, 27 | "minimum-stability": "dev" 28 | } 29 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafasamp/sonus/bdf49840c5de4e72a7b767eec399d3338f1f9779/public/.gitkeep -------------------------------------------------------------------------------- /src/Rafasamp/Sonus/Facade.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | 14 | class Sonus 15 | { 16 | /** 17 | * Returns full path of ffmpeg 18 | * @return string 19 | */ 20 | protected static function getConverterPath() 21 | { 22 | return Config::get('sonus::ffmpeg'); 23 | } 24 | 25 | /** 26 | * Returns full path of ffprobe 27 | * @return string 28 | */ 29 | protected static function getProbePath() 30 | { 31 | return Config::get('sonus::ffprobe'); 32 | } 33 | 34 | /** 35 | * Returns full path for progress temp files 36 | * @return [type] [description] 37 | */ 38 | protected static function getTempPath() 39 | { 40 | return Config::get('sonus::tmp_dir'); 41 | } 42 | 43 | /** 44 | * Returns installed ffmpeg version 45 | * @return array 46 | */ 47 | public static function getConverterVersion() 48 | { 49 | // Run terminal command to retrieve version 50 | $command = self::getConverterPath().' -version'; 51 | $output = shell_exec($command); 52 | 53 | // PREG pattern to retrive version information 54 | $ouput = preg_match("/ffmpeg version (?P[0-9]{0,3}).(?P[0-9]{0,3}).(?P[0-9]{0,3})/", $output, $parsed); 55 | 56 | // Verify output 57 | if ($output === false || $output == 0) 58 | { 59 | return false; 60 | } 61 | 62 | // Assign array with variables 63 | $version = array( 64 | 'major' => $parsed['major'], 65 | 'minor' => $parsed['minor'], 66 | 'rev' => $parsed['revision'] 67 | ); 68 | 69 | return $version; 70 | } 71 | 72 | /** 73 | * Returns all formats ffmpeg supports 74 | * @return array 75 | */ 76 | public static function getSupportedFormats() 77 | { 78 | // Run terminal command 79 | $command = self::getConverterPath().' -formats'; 80 | $output = shell_exec($command); 81 | 82 | // PREG pattern to retrive version information 83 | $output = preg_match_all("/(?P(D\s|\sE|DE))\s(?P\S{3,11})\s/", $output, $parsed); 84 | 85 | // Verify output 86 | if ($output === false || $output == 0) 87 | { 88 | return false; 89 | } 90 | 91 | // Combine the format and mux information into an array 92 | $formats = array_combine($parsed['format'], $parsed['mux']); 93 | 94 | return $formats; 95 | } 96 | 97 | /** 98 | * Returns all audio formats ffmpeg can encode 99 | * @return array 100 | */ 101 | public static function getSupportedAudioEncoders() 102 | { 103 | // Run terminal command 104 | $command = self::getConverterPath().' -encoders'; 105 | $output = shell_exec($command); 106 | 107 | // PREG pattern to retrive version information 108 | $output = preg_match_all("/[A]([.]|\w)([.]|\w)([.]|\w)([.]|\w)([.]|\w)\s(?P\S{3,20})\s/", $output, $parsed); 109 | 110 | // Verify output 111 | if ($output === false || $output == 0) 112 | { 113 | return false; 114 | } 115 | 116 | return $parsed['format']; 117 | } 118 | 119 | /** 120 | * Returns all video formats ffmpeg can encode 121 | * @return array 122 | */ 123 | public static function getSupportedVideoEncoders() 124 | { 125 | // Run terminal command 126 | $command = self::getConverterPath().' -encoders'; 127 | $output = shell_exec($command); 128 | 129 | // PREG pattern to retrive version information 130 | $output = preg_match_all("/[V]([.]|\w)([.]|\w)([.]|\w)([.]|\w)([.]|\w)\s(?P\S{3,20})\s/", $output, $parsed); 131 | 132 | // Verify output 133 | if ($output === false || $output == 0) 134 | { 135 | return false; 136 | } 137 | 138 | return $parsed['format']; 139 | } 140 | 141 | /** 142 | * Returns all audio formats ffmpeg can decode 143 | * @return array 144 | */ 145 | public static function getSupportedAudioDecoders() 146 | { 147 | // Run terminal command 148 | $command = self::getConverterPath().' -decoders'; 149 | $output = shell_exec($command); 150 | 151 | // PREG pattern to retrive version information 152 | $output = preg_match_all("/[A]([.]|\w)([.]|\w)([.]|\w)([.]|\w)([.]|\w)\s(?P\w{3,20})\s/", $output, $parsed); 153 | 154 | // Verify output 155 | if ($output === false || $output == 0) 156 | { 157 | return false; 158 | } 159 | 160 | return $parsed['format']; 161 | } 162 | 163 | /** 164 | * Returns all video formats ffmpeg can decode 165 | * @return array 166 | */ 167 | public static function getSupportedVideoDecoders() 168 | { 169 | // Run terminal command 170 | $command = self::getConverterPath().' -decoders'; 171 | $output = shell_exec($command); 172 | 173 | // PREG pattern to retrive version information 174 | $output = preg_match_all("/[V]([.]|\w)([.]|\w)([.]|\w)([.]|\w)([.]|\w)\s(?P\w{3,20})\s/", $output, $parsed); 175 | 176 | // Verify output 177 | if ($output === false || $output == 0) 178 | { 179 | return false; 180 | } 181 | 182 | return $parsed['format']; 183 | } 184 | 185 | /** 186 | * Returns boolean if ffmpeg is able to encode to this format 187 | * @param string $format ffmpeg format name 188 | * @return boolean 189 | */ 190 | public static function canEncode($format) 191 | { 192 | $formats = array_merge(self::getSupportedAudioEncoders(), self::getSupportedVideoEncoders()); 193 | 194 | // Return boolean if they can be encoded or not 195 | if(!in_array($format, $formats)) 196 | { 197 | return false; 198 | } else { 199 | return true; 200 | } 201 | } 202 | 203 | /** 204 | * Returns boolean if ffmpeg is able to decode to this format 205 | * @param string $format ffmpeg format name 206 | * @return boolean 207 | */ 208 | public static function canDecode($format) 209 | { 210 | // Get an array with all supported encoding formats 211 | $formats = array_merge(self::getSupportedAudioDecoders(), self::getSupportedVideoDecoders()); 212 | 213 | // Return boolean if they can be encoded or not 214 | if(!in_array($format, $formats)) 215 | { 216 | return false; 217 | } else { 218 | return true; 219 | } 220 | } 221 | 222 | /** 223 | * Returns array with file information 224 | * @param string $input file input 225 | * @param string $type output format 226 | * @return array, json, xml, csv 227 | */ 228 | public static function getMediaInfo($input, $type = null) 229 | { 230 | // Just making sure everything goes smooth 231 | if (substr($input, 0, 2) == '-i') 232 | { 233 | $input = substr($input, 3); 234 | } 235 | 236 | switch ($type) 237 | { 238 | case 'json': 239 | $command = self::getProbePath().' -v quiet -print_format json -show_format -show_streams -pretty -i '.$input.' 2>&1'; 240 | $output = shell_exec($command); 241 | $output = json_decode($output, true); 242 | break; 243 | 244 | case 'xml': 245 | $command = self::getProbePath().' -v quiet -print_format xml -show_format -show_streams -pretty -i '.$input.' 2>&1'; 246 | $output = shell_exec($command); 247 | break; 248 | 249 | case 'csv': 250 | $command = self::getProbePath().' -v quiet -print_format csv -show_format -show_streams -pretty -i '.$input.' 2>&1'; 251 | $output = shell_exec($command); 252 | break; 253 | 254 | default: 255 | $command = self::getProbePath().' -v quiet -print_format json -show_format -show_streams -pretty -i '.$input.' 2>&1'; 256 | $output = shell_exec($command); 257 | $output = json_decode($output, true); 258 | break; 259 | } 260 | 261 | return $output; 262 | } 263 | 264 | /** 265 | * Retrieves video thumbnails 266 | * @param string $input video input 267 | * @param string $output output filename 268 | * @param integer $count number of thumbnails to generate 269 | * @param string $format thumbnail format 270 | * @return boolean 271 | */ 272 | public static function getThumbnails($input, $output, $count = 5, $format = 'png') 273 | { 274 | // Round user input 275 | $count = round($count); 276 | 277 | // Return false if user requests 0 frames or round function fails 278 | if ($count < 1) 279 | { 280 | return false; 281 | } 282 | 283 | // Execute thumbnail generator command 284 | $command = self::getConverterPath().' -i '.$input.' -vf "select=gt(scene\,0.5)" -frames:v '.$count.' -vsync vfr '.$output.'%02d.png'; 285 | shell_exec($command); 286 | return true; 287 | } 288 | 289 | /** 290 | * Input files 291 | * @var array 292 | */ 293 | protected $input = array(); 294 | 295 | /** 296 | * Output files 297 | * @var array 298 | */ 299 | protected $output = array(); 300 | 301 | /** 302 | * Contains the combination of all parameters set by the user 303 | * @var array 304 | */ 305 | protected $parameters = array(); 306 | 307 | /** 308 | * Contains the job progress id 309 | * @var string 310 | */ 311 | protected $progress; 312 | 313 | /** 314 | * Returns object instance for chainable methods 315 | * @return object 316 | */ 317 | public static function convert() 318 | { 319 | $sonus = new Sonus; 320 | return $sonus; 321 | } 322 | 323 | /** 324 | * Sets the progress ID 325 | * @param string $var progress id 326 | * @return null 327 | */ 328 | public function progress($var) 329 | { 330 | // If value is null pass current timestamp 331 | if (is_null($var)) 332 | { 333 | $this->progress = date('U'); 334 | return $this; 335 | } else { 336 | $this->progress = $var; 337 | return $this; 338 | } 339 | } 340 | 341 | /** 342 | * Adds an input file 343 | * @param string $var filename 344 | * @return boolean 345 | */ 346 | public function input($var) 347 | { 348 | // Value must be text 349 | if (!is_string($var)) 350 | { 351 | return false; 352 | } 353 | 354 | array_push($this->input, '-i '.$var); 355 | return $this; 356 | } 357 | 358 | /** 359 | * Adds an output file 360 | * @param string $var filename 361 | * @return boolean 362 | */ 363 | public function output($var) 364 | { 365 | // Value must be text 366 | if (!is_string($var)) 367 | { 368 | return false; 369 | } 370 | 371 | array_push($this->output, $var); 372 | return $this; 373 | } 374 | 375 | /** 376 | * Overwrite output file if it exists 377 | * @param boolean $var 378 | * @return boolean 379 | */ 380 | public function overwrite($var = true) 381 | { 382 | switch ($var) 383 | { 384 | case true: 385 | array_push($this->parameters, '-y'); 386 | return $this; 387 | break; 388 | 389 | case false: 390 | array_push($this->parameters, '-n'); 391 | return $this; 392 | break; 393 | 394 | default: 395 | return false; 396 | break; 397 | } 398 | } 399 | 400 | /** 401 | * Stop running FFMPEG after X seconds 402 | * @param int $var seconds 403 | * @return boolean 404 | */ 405 | public function timelimit($var) 406 | { 407 | // Value must be numeric 408 | if (!is_numeric($var)) 409 | { 410 | return false; 411 | } 412 | 413 | array_push($this->parameters, '-timelimit '.$var); 414 | return $this; 415 | } 416 | 417 | /** 418 | * Sets the codec used for the conversion 419 | * https://trac.ffmpeg.org/wiki/AACEncodingGuide 420 | * https://trac.ffmpeg.org/wiki/Encoding%20VBR%20(Variable%20Bit%20Rate)%20mp3%20audio 421 | * @param string $var ffmpeg codec name 422 | * @return boolean 423 | */ 424 | public function codec($var, $type = 'audio') 425 | { 426 | // Value must not be null 427 | if (is_null($var)) 428 | { 429 | return false; 430 | } 431 | 432 | switch($type) 433 | { 434 | case 'audio': 435 | array_push($this->parameters, '-c:a '.$var); 436 | return $this; 437 | break; 438 | 439 | case 'video': 440 | array_push($this->parameters, '-c:v '.$var); 441 | return $this; 442 | break; 443 | 444 | default: 445 | return false; 446 | break; 447 | } 448 | } 449 | 450 | /** 451 | * Sets the constant bitrate 452 | * @param int $var bitrate 453 | * @return boolean 454 | */ 455 | public function bitrate($var, $type = 'audio') 456 | { 457 | // Value must be numeric 458 | if (!is_numeric($var)) 459 | { 460 | return false; 461 | } 462 | 463 | switch ($type) 464 | { 465 | case 'audio': 466 | array_push($this->parameters, '-b:a '.$var.'k'); 467 | return $this; 468 | break; 469 | 470 | case 'video': 471 | array_push($this->parameters, '-b:v '.$var.'k'); 472 | return $this; 473 | break; 474 | 475 | default: 476 | return false; 477 | break; 478 | } 479 | } 480 | 481 | /** 482 | * Sets the number of audio channels 483 | * https://trac.ffmpeg.org/wiki/AudioChannelManipulation 484 | * @param string $var 485 | * @return boolean 486 | */ 487 | public function channels($var) 488 | { 489 | // Value must be numeric 490 | if (!is_numeric($var)) 491 | { 492 | return false; 493 | } 494 | 495 | array_push($this->parameters, '-ac '.$var); 496 | return $this; 497 | } 498 | 499 | /** 500 | * Sets audio frequency rate 501 | * http://ffmpeg.org/ffmpeg.html#Audio-Options 502 | * @param int $var frequency 503 | * @return boolean 504 | */ 505 | public function frequency($var) 506 | { 507 | // Value must be numeric 508 | if (!is_numeric($var)) 509 | { 510 | return false; 511 | } 512 | 513 | array_push($this->parameters, '-ar:a '.$var); 514 | return $this; 515 | } 516 | 517 | /** 518 | * Performs conversion 519 | * @param string $arg user arguments 520 | * @return string tracking code 521 | * @return boolean false on error 522 | */ 523 | public function go($arg = null) 524 | { 525 | // Assign converter path 526 | $ffmpeg = self::getConverterPath(); 527 | 528 | // Check if user provided raw arguments 529 | if (is_null($arg)) 530 | { 531 | // If not, use the prepared arguments 532 | $arg = implode(' ', $this->parameters); 533 | } 534 | 535 | // Return input and output files 536 | $input = implode(' ', $this->input); 537 | $output = implode(' ', $this->output); 538 | 539 | // Prepare the command 540 | $cmd = escapeshellcmd($ffmpeg.' '.$input.' '.$arg.' '.$output); 541 | 542 | // Check if progress reporting is enabled 543 | if (Config::get('sonus::progress') === true) 544 | { 545 | // Get temp dir 546 | $tmpdir = self::getTempPath(); 547 | 548 | // Get progress id 549 | if (empty($this->progress)) 550 | { 551 | // Create a default (unix timestamp) 552 | $progress = date('U'); 553 | } else { 554 | // Assign if it exists 555 | $progress = $this->progress; 556 | } 557 | 558 | // Publish progress to this ID 559 | $cmd = $cmd.' 1>"'.$tmpdir.$progress.'.sonustmp" 2>&1'; 560 | 561 | // Execute command 562 | return shell_exec($cmd); 563 | } else { 564 | // Execute command 565 | return shell_exec($cmd); 566 | } 567 | } 568 | 569 | /** 570 | * Returns given job progress 571 | * @param string $job id 572 | * @param string $format format to output data 573 | * @return array 574 | */ 575 | public static function getProgress($job, $format = null) 576 | { 577 | // Get the temporary directory 578 | $tmpdir = self::getTempPath(); 579 | 580 | // The code below has been adapted from Jimbo 581 | // http://stackoverflow.com/questions/11441517/ffmpeg-progress-bar-encoding-percentage-in-php 582 | $content = @file_get_contents($tmpdir.$job.'.sonustmp'); 583 | 584 | if($content) 585 | { 586 | // Get duration of source 587 | preg_match("/Duration: (.*?), start:/", $content, $matches); 588 | 589 | $rawDuration = $matches[1]; 590 | 591 | // rawDuration is in 00:00:00.00 format. This converts it to seconds. 592 | $ar = array_reverse(explode(":", $rawDuration)); 593 | $duration = floatval($ar[0]); 594 | if (!empty($ar[1])) $duration += intval($ar[1]) * 60; 595 | if (!empty($ar[2])) $duration += intval($ar[2]) * 60 * 60; 596 | 597 | // Get the time in the file that is already encoded 598 | preg_match_all("/time=(.*?) bitrate/", $content, $matches); 599 | 600 | $rawTime = array_pop($matches); 601 | 602 | // This is needed if there is more than one match 603 | if (is_array($rawTime)) 604 | { 605 | $rawTime = array_pop($rawTime); 606 | } 607 | 608 | // rawTime is in 00:00:00.00 format. This converts it to seconds. 609 | $ar = array_reverse(explode(":", $rawTime)); 610 | $time = floatval($ar[0]); 611 | if (!empty($ar[1])) $time += intval($ar[1]) * 60; 612 | if (!empty($ar[2])) $time += intval($ar[2]) * 60 * 60; 613 | 614 | // Calculate the progress 615 | $progress = round(($time/$duration) * 100); 616 | 617 | // Output to array 618 | $output = array( 619 | 'Duration' => $rawDuration, 620 | 'Current' => $rawTime, 621 | 'Progress' => $progress 622 | ); 623 | 624 | // Return data 625 | switch ($format) 626 | { 627 | case 'array': 628 | return $output; 629 | break; 630 | 631 | default: 632 | return json_encode($output); 633 | break; 634 | } 635 | } else { 636 | return null; 637 | } 638 | } 639 | 640 | /** 641 | * Deletes job temporary file 642 | * @param string $job id 643 | * @return boolean 644 | */ 645 | public static function destroyProgress($job) 646 | { 647 | // Get temporary file path 648 | $file = $tmpdir.$job.'.sonustmp'; 649 | 650 | // Check if file exists 651 | if (is_file($file)) 652 | { 653 | // Delete file 654 | $output = unlink($tmpdir.$job.'.sonustmp'); 655 | return $output; 656 | } else { 657 | return false; 658 | } 659 | } 660 | 661 | /** 662 | * Deletes all temporary files 663 | * @return boolean 664 | */ 665 | public static function destroyAllProgress() 666 | { 667 | // Get all filenames within the temporary folder 668 | $files = glob($tmpdir.'*'); 669 | 670 | // Iterate through files 671 | $output = array(); 672 | foreach ($files as $file) 673 | { 674 | if (is_file($file)) 675 | { 676 | // Return result to array 677 | $result = unlink($file); 678 | array_push($output, var_export($result, true)); 679 | } 680 | } 681 | 682 | // If a file could not be deleted, return false 683 | if (array_search('false', $output)) 684 | { 685 | return false; 686 | } 687 | 688 | return true; 689 | } 690 | } -------------------------------------------------------------------------------- /src/Rafasamp/Sonus/SonusServiceProvider.php: -------------------------------------------------------------------------------- 1 | package('rafasamp/sonus'); 22 | } 23 | 24 | /** 25 | * Register the service provider. 26 | * 27 | * @return void 28 | */ 29 | public function register() 30 | { 31 | $this->app['sonus'] = $this->app->share(function($app) 32 | { 33 | return new Sonus; 34 | }); 35 | } 36 | 37 | /** 38 | * Get the services provided by the provider. 39 | * 40 | * @return array 41 | */ 42 | public function provides() 43 | { 44 | return array('sonus'); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/config/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafasamp/sonus/bdf49840c5de4e72a7b767eec399d3338f1f9779/src/config/.gitkeep -------------------------------------------------------------------------------- /src/config/config.php: -------------------------------------------------------------------------------- 1 | '', 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | ffprobe System Path 26 | |-------------------------------------------------------------------------- 27 | | 28 | | We need to know the fully qualified system path to where ffprobe 29 | | lives on this server. If you paste this path into your shell or 30 | | command prompt you should get output from ffprobe. 31 | | 32 | */ 33 | 34 | 'ffprobe' => '', 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Progress monitoring 39 | |-------------------------------------------------------------------------- 40 | | 41 | | FFMPEG supports outputing progress to HTTP. Problem is, PHP can't really 42 | | handle chunked POST requests. Therefore the solution is to output progress 43 | | to a text file and track the job by reading it live. 44 | | 45 | | If you would like to let your users know of the progress on active running 46 | | conversions set this flag to true. 47 | | 48 | */ 49 | 50 | 'progress' => false, 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Temporary Directory 55 | |-------------------------------------------------------------------------- 56 | | 57 | | In order to monitor the progress of running tasks Sonus will need to write 58 | | temporary files during the encoding progress. Please set a directory where 59 | | these can be written to, but make sure PHP is able to read and write to it. 60 | | 61 | | Make sure that your path has a trailing slash! 62 | | 63 | | Examples: 64 | | Windows: 'C:/ffmpeg/tmp/' 65 | | Mac OSX: '/Applications/MAMP/ffmpeg/tmp/' 66 | | Linux: '/var/www/tmp/' 67 | | 68 | */ 69 | 70 | 'tmp_dir' => '' 71 | ); -------------------------------------------------------------------------------- /src/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafasamp/sonus/bdf49840c5de4e72a7b767eec399d3338f1f9779/src/controllers/.gitkeep -------------------------------------------------------------------------------- /src/lang/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafasamp/sonus/bdf49840c5de4e72a7b767eec399d3338f1f9779/src/lang/.gitkeep -------------------------------------------------------------------------------- /src/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafasamp/sonus/bdf49840c5de4e72a7b767eec399d3338f1f9779/src/migrations/.gitkeep -------------------------------------------------------------------------------- /src/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafasamp/sonus/bdf49840c5de4e72a7b767eec399d3338f1f9779/src/views/.gitkeep -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafasamp/sonus/bdf49840c5de4e72a7b767eec399d3338f1f9779/tests/.gitkeep -------------------------------------------------------------------------------- /tests/HelpersTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($result == $expected); 18 | } 19 | 20 | /** 21 | * Seconds to Timestamp function 22 | * 23 | * @return void 24 | */ 25 | public function testSecondsToTimestamp() 26 | { 27 | $result = Helpers::secondsToTimestamp(3661); 28 | $expected = '01:01:01'; 29 | $this->assertTrue($result == $expected); 30 | } 31 | 32 | /** 33 | * Progress Percentage function 34 | * 35 | * @return void 36 | */ 37 | public function testProgressPercentage() 38 | { 39 | $result = Helpers::progressPercentage(3660, 3660); 40 | $expected = 100; 41 | $this->assertTrue($result == $expected); 42 | } 43 | } -------------------------------------------------------------------------------- /tests/SonusTest.php: -------------------------------------------------------------------------------- 1 | sonus = new Sonus; 23 | } 24 | 25 | /** 26 | * Input function must receive a string 27 | * 28 | * @return void 29 | */ 30 | public function testInputMustBeAString() 31 | { 32 | $result = $this->sonus->input(1); 33 | $expected = false; 34 | $this->assertTrue($result == $expected); 35 | } 36 | 37 | /** 38 | * Output function must receive a string 39 | * 40 | * @return void 41 | */ 42 | public function testOutputMustBeAString() 43 | { 44 | $result = $this->sonus->output(1); 45 | $expected = false; 46 | $this->assertTrue($result == $expected); 47 | } 48 | 49 | /** 50 | * Timelimit function must receive a number 51 | * 52 | * @return void 53 | */ 54 | public function testTimelimitMustBeNumeric() 55 | { 56 | $result = $this->sonus->timelimit('word'); 57 | $expected = false; 58 | $this->assertTrue($result == $expected); 59 | } 60 | 61 | /** 62 | * Codec function must not receive a null value for codec name 63 | * 64 | * @return void 65 | */ 66 | public function testCodecNameMustNotBeNull() 67 | { 68 | $result = $this->sonus->codec('', 'test'); 69 | $expected = false; 70 | $this->assertTrue($result == $expected); 71 | } 72 | 73 | /** 74 | * Codec function must receive a type of audio or video 75 | * 76 | * @return void 77 | */ 78 | public function testCodecTypeMustBeAudioOrVideo() 79 | { 80 | $result = $this->sonus->codec('test', 'test'); 81 | $expected = false; 82 | $this->assertTrue($result == $expected); 83 | } 84 | 85 | /** 86 | * Bitrate function must receive a number 87 | * 88 | * @return void 89 | */ 90 | public function testBitrateMustBeNumeric() 91 | { 92 | $result = $this->sonus->bitrate('128kbps', 'audio'); 93 | $expected = false; 94 | $this->assertTrue($result == $expected); 95 | } 96 | 97 | /** 98 | * Bitrate function must receive a type of audio or video 99 | * 100 | * @return void 101 | */ 102 | public function testBitrateTypeMustBeAudioOrVideo() 103 | { 104 | $result = $this->sonus->codec('128', 'sound'); 105 | $expected = false; 106 | $this->assertTrue($result == $expected); 107 | } 108 | 109 | /** 110 | * Channels function must receive a number 111 | * 112 | * @return void 113 | */ 114 | public function testChannelsMustBeNumeric() 115 | { 116 | $result = $this->sonus->channels('stereo'); 117 | $expected = false; 118 | $this->assertTrue($result == $expected); 119 | } 120 | 121 | /** 122 | * Frequency function must receive a number 123 | * 124 | * @return void 125 | */ 126 | public function testFrequencyMustBeNumeric() 127 | { 128 | $result = $this->sonus->frequency('44000hz'); 129 | $expected = false; 130 | $this->assertTrue($result == $expected); 131 | } 132 | } --------------------------------------------------------------------------------