├── .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 | [](https://packagist.org/packages/rafasamp/sonus)
8 | [](https://travis-ci.org/rafasamp/sonus)
9 | [](https://packagist.org/packages/rafasamp/sonus)
10 | [](http://stillmaintained.com/rafasamp/sonus)
11 | [](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 | }
--------------------------------------------------------------------------------