Merge branch 'release-0.5.1'

This commit is contained in:
Pierre Rudloff 2016-08-29 12:35:30 +02:00
commit a92838b7ce
29 changed files with 2103 additions and 485 deletions

2
.gitignore vendored
View file

@ -10,3 +10,5 @@ alltube-*.zip
coverage/ coverage/
bower_components/ bower_components/
config.yml config.yml
docs/
clover.xml

View file

@ -1,6 +1,7 @@
language: php language: php
install: install:
- composer install - composer install --no-dev
- npm install
before_install: before_install:
- composer selfupdate - composer selfupdate
after_success:
- bash <(curl -s https://codecov.io/bash)

6
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,6 @@
## Report a bug
Before opening a new issue, make sure that:
* It has not already been [reported](https://github.com/Rudloff/alltube/issues).
* You read the [README](README.md) and the [FAQ](FAQ.md).
* You can provide **logs**.

29
FAQ.md Normal file
View file

@ -0,0 +1,29 @@
# Frequently asked questions
## My browser plays the video. How do I download it?
Most recent browsers automatically play a video if it is a format they know how to play.
You can ususally download the video by doing *File > Save to* or *ctrl + S*.
## How do I change config parameters?
You need to create a YAML file called `config.yml` at the root of your project.
Here are the parameters that you can set:
* youtubedl: path to your youtube-dl binary
* python: path to your python binary
* params: an array of parameters to pass to youtube-dl
* curl_params: an array of parameters to pass to curl
* convert: true to enable audio conversion
* avconv: path to your avconv or ffmpeg binary
* rtmpdump: path to your rtmpdump binary
See [config.example.yml](config.example.yml) for default values.
## How do I enable audio conversion?
In order to enable audio conversion, you need to add this to your `config.yml` file:
```yaml
convert: true
avconv: path/to/avconv
```
You will also need to install `avconv` and `curl` on your server:
```bash
sudo apt-get install libav-tools curl
```

View file

@ -34,7 +34,8 @@ module.exports = function (grunt) {
}, },
phpcs: { phpcs: {
options: { options: {
standard: 'PSR2' standard: 'PSR2',
bin: 'vendor/bin/phpcs'
}, },
php: { php: {
src: ['*.php', 'classes/*.php', 'controllers/*.php'] src: ['*.php', 'classes/*.php', 'controllers/*.php']
@ -52,6 +53,12 @@ module.exports = function (grunt) {
} }
}, },
phpunit: { phpunit: {
options: {
bin: 'php -dzend_extension=xdebug.so ./vendor/bin/phpunit',
stopOnError: true,
stopOnFailure: true,
followOutput: true
},
classes: { classes: {
dir: 'tests/' dir: 'tests/'
} }
@ -61,7 +68,27 @@ module.exports = function (grunt) {
options: { options: {
archive: 'alltube-<%= githash.main.tag %>.zip' archive: 'alltube-<%= githash.main.tag %>.zip'
}, },
src: ['*.php', '!config.yml', 'dist/**', 'fonts/**', '.htaccess', 'img/**', 'js/**', 'LICENSE', 'README.md', 'robots.txt', 'sitemap.xml', 'templates/**', 'templates_c/', 'vendor/**', 'classes/**', 'controllers/**', 'bower_components/**', '!vendor/ffmpeg/**', '!vendor/bin/ffmpeg'] src: ['*.php', '!config.yml', 'dist/**', '.htaccess', 'img/**', 'LICENSE', 'README.md', 'robots.txt', 'sitemap.xml', 'templates/**', 'templates_c/', 'vendor/**', 'classes/**', 'controllers/**', 'bower_components/**', '!vendor/ffmpeg/**', '!vendor/bin/ffmpeg']
}
},
phpdocumentor: {
doc: {
options: {
directory: 'classes/,controllers/,tests/'
}
}
},
jsonlint: {
manifests: {
src: ['*.json', '*.webapp'],
options: {
format: true
}
}
},
fixpack: {
package: {
src: 'package.json'
} }
} }
} }
@ -75,9 +102,13 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-phpunit'); grunt.loadNpmTasks('grunt-phpunit');
grunt.loadNpmTasks('grunt-contrib-compress'); grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-jslint'); grunt.loadNpmTasks('grunt-jslint');
grunt.loadNpmTasks('grunt-phpdocumentor');
grunt.loadNpmTasks('grunt-jsonlint');
grunt.loadNpmTasks('grunt-fixpack');
grunt.registerTask('default', ['uglify', 'cssmin']); grunt.registerTask('default', ['uglify', 'cssmin']);
grunt.registerTask('lint', ['phpcs', 'jslint']); grunt.registerTask('lint', ['jslint', 'fixpack', 'jsonlint', 'phpcs']);
grunt.registerTask('test', ['phpunit']); grunt.registerTask('test', ['phpunit']);
grunt.registerTask('doc', ['phpdocumentor']);
grunt.registerTask('release', ['default', 'githash', 'compress']); grunt.registerTask('release', ['default', 'githash', 'compress']);
}; };

111
README.md
View file

@ -1,11 +1,11 @@
alltube Alltube Download
======= =======
HTML GUI for youtube-dl (http://alltubedownload.net/) HTML GUI for youtube-dl (http://alltubedownload.net/)
![Screenshot](img/screenshot.png "Alltube GUI screenshot") ![Screenshot](img/screenshot.png "Alltube GUI screenshot")
##Setup ## Setup
### From a release package ### From a release package
You can download the latest release package [here](https://github.com/Rudloff/alltube/releases). You can download the latest release package [here](https://github.com/Rudloff/alltube/releases).
@ -14,93 +14,96 @@ You just have to unzip it on your server and it should be ready to use.
### From Git ### From Git
In order to get AllTube working, you need to use [npm](https://www.npmjs.com/) and [Composer](https://getcomposer.org/): In order to get AllTube working, you need to use [npm](https://www.npmjs.com/) and [Composer](https://getcomposer.org/):
```bash
npm install npm install
composer install composer install
```
This will download all the required dependencies. This will download all the required dependencies.
(Note that it will download the ffmpeg binary for 64-bits Linux. If you are on another platform, you might want to specify the path to avconv/ffmpeg in your config file.) (Note that it will download the ffmpeg binary for 64-bits Linux. If you are on another platform, you might want to specify the path to avconv/ffmpeg in your config file.)
You should also ensure that the *templates_c* folder has the right permissions: You should also ensure that the *templates_c* folder has the right permissions:
```bash
chmod 777 templates_c/ chmod 777 templates_c/
```
If your web server is Apache, you need to set the `AllowOverride` setting to `All` or `FileInfo`. If your web server is Apache, you need to set the `AllowOverride` setting to `All` or `FileInfo`.
##Config ## Config
If you want to use a custom config, you need to create a config file: If you want to use a custom config, you need to create a config file:
```bash
cp config.example.yml config.yml
```
cp config.example.yml config.yml ## Web server configuration
### Apache
##Web server configuration
###Apache
You will need the following modules: You will need the following modules:
* mod_mime * mod_mime
* mod_rewrite * mod_rewrite
###Nginx ### Nginx
Here is an exemple Nginx configuration: Here is an exemple Nginx configuration:
```nginx
server {
server_name localhost;
listen 443 ssl;
server { root /var/www/path/to/alltube;
server_name localhost; index index.php;
listen 443 ssl;
root /var/www/path/to/alltube; access_log /var/log/nginx/alltube.access.log;
index index.php; error_log /var/log/nginx/alltube.error.log;
access_log /var/log/nginx/alltube.access.log; types {
error_log /var/log/nginx/alltube.error.log; text/html html htm shtml;
text/css css;
text/xml xml;
application/x-web-app-manifest+json webapp;
}
types { # Deny access to dotfiles
text/html html htm shtml; location ~ /\. {
text/css css; deny all;
text/xml xml; }
application/x-web-app-manifest+json webapp;
}
# Deny access to dotfiles location / {
location ~ /\. { try_files $uri /index.php?$args;
deny all; }
}
location / { location ~ \.php$ {
try_files $uri /index.php?$args; try_files $uri /index.php?$args;
}
location ~ \.php$ { fastcgi_param PATH_INFO $fastcgi_path_info;
try_files $uri /index.php?$args; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_intercept_errors off;
fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_buffer_size 16k;
fastcgi_index index.php; fastcgi_buffers 4 16k;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k; include fastcgi_params;
fastcgi_buffers 4 16k; }
}
```
include fastcgi_params; ## License
}
}
##License
This software is available under the [GNU General Public License](http://www.gnu.org/licenses/gpl.html). This software is available under the [GNU General Public License](http://www.gnu.org/licenses/gpl.html).
__Please use a different name and logo if you run it on a public server.__ __Please use a different name and logo if you run it on a public server.__
##Other dependencies ## Other dependencies
You need [avconv](https://libav.org/avconv.html), [rtmpdump](http://rtmpdump.mplayerhq.hu/) and [curl](https://curl.haxx.se/) in order to enable conversions. You need [avconv](https://libav.org/avconv.html), [rtmpdump](http://rtmpdump.mplayerhq.hu/) and [curl](https://curl.haxx.se/) in order to enable conversions.
If you don't want to enable conversions, you can disable it in *config.yml*. If you don't want to enable conversions, you can disable it in *config.yml*.
On Debian-based systems: On Debian-based systems:
```bash
sudo apt-get install libav-tools rtmpdump curl sudo apt-get install libav-tools rtmpdump curl
```
You also probably need to edit the *avconv* variable in *config.yml* so that it points to your ffmpeg/avconv binary (*/usr/bin/avconv* on Debian/Ubuntu). You also probably need to edit the *avconv* variable in *config.yml* so that it points to your ffmpeg/avconv binary (*/usr/bin/avconv* on Debian/Ubuntu).

View file

@ -1,6 +1,6 @@
{ {
"name": "alltube", "name": "alltube",
"dependencies": { "dependencies": {
"opensans-googlefont": "*" "opensans-googlefont": "*"
} }
} }

View file

@ -1,53 +1,83 @@
<?php <?php
/** /**
* Config class * Config class
* */
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
namespace Alltube; namespace Alltube;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
/** /**
* Class to manage config parameters * Manage config parameters
* */
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
class Config class Config
{ {
/**
* Singleton instance
* @var Config
*/
private static $instance; private static $instance;
/**
* youtube-dl binary path
* @var string
*/
public $youtubedl = 'vendor/rg3/youtube-dl/youtube_dl/__main__.py'; public $youtubedl = 'vendor/rg3/youtube-dl/youtube_dl/__main__.py';
/**
* python binary path
* @var string
*/
public $python = '/usr/bin/python'; public $python = '/usr/bin/python';
/**
* youtube-dl parameters
* @var array
*/
public $params = array('--no-playlist', '--no-warnings', '-f best[protocol^=http]', '--playlist-end', 1); public $params = array('--no-playlist', '--no-warnings', '-f best[protocol^=http]', '--playlist-end', 1);
/**
* Enable audio conversion
* @var bool
*/
public $convert = false; public $convert = false;
/**
* avconv or ffmpeg binary path
* @var string
*/
public $avconv = 'vendor/bin/ffmpeg'; public $avconv = 'vendor/bin/ffmpeg';
/**
* rtmpdump binary path
* @var string
*/
public $rtmpdump = 'vendor/bin/rtmpdump'; public $rtmpdump = 'vendor/bin/rtmpdump';
/**
* curl binary path
* @var string
*/
public $curl = '/usr/bin/curl';
/**
* curl parameters
* @var array
*/
public $curl_params = array(); public $curl_params = array();
private $configFile;
/** /**
* Config constructor * Config constructor
*/ */
private function __construct() private function __construct($yamlfile)
{ {
$yamlfile = __DIR__.'/../config.yml'; $this->file = $yamlfile;
if (is_file($yamlfile)) { if (is_file($yamlfile)) {
$yaml = Yaml::parse(file_get_contents($yamlfile)); $yaml = Yaml::parse(file_get_contents($yamlfile));
if (isset($yaml) && is_array($yaml)) { if (isset($yaml) && is_array($yaml)) {
foreach ($yaml as $param => $value) { foreach ($yaml as $param => $value) {
if (isset($this->$param)) { if (isset($this->$param) && isset($value)) {
$this->$param = $value; $this->$param = $value;
} }
} }
@ -63,11 +93,21 @@ class Config
* *
* @return Config * @return Config
*/ */
public static function getInstance() public static function getInstance($yamlfile = 'config.yml')
{ {
if (is_null(self::$instance)) { $yamlfile = __DIR__.'/../'.$yamlfile;
self::$instance = new Config(); if (is_null(self::$instance) || self::$instance->file != $yamlfile) {
self::$instance = new Config($yamlfile);
} }
return self::$instance; return self::$instance;
} }
/**
* Destroy singleton instance
* @return void
*/
public static function destroyInstance()
{
self::$instance = null;
}
} }

View file

@ -1,33 +1,22 @@
<?php <?php
/** /**
* VideoDownload class * VideoDownload class
* */
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
namespace Alltube; namespace Alltube;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder; use Symfony\Component\Process\ProcessBuilder;
use Chain\Chain;
/** /**
* Main class * Extract info about videos
* */
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
class VideoDownload class VideoDownload
{ {
/**
* VideoDownload constructor
*/
public function __construct() public function __construct()
{ {
$this->config = Config::getInstance(); $this->config = Config::getInstance();
@ -43,7 +32,7 @@ class VideoDownload
/** /**
* List all extractors * List all extractors
* *
* @return array Extractors * @return string[] Extractors
* */ * */
public function listExtractors() public function listExtractors()
{ {
@ -54,7 +43,7 @@ class VideoDownload
); );
$process = $this->procBuilder->getProcess(); $process = $this->procBuilder->getProcess();
$process->run(); $process->run();
return explode(PHP_EOL, $process->getOutput()); return explode(PHP_EOL, trim($process->getOutput()));
} }
/** /**
@ -111,6 +100,145 @@ class VideoDownload
} else { } else {
return $process->getOutput(); return $process->getOutput();
} }
}
/**
* Get filename of video file from URL of page
*
* @param string $url URL of page
* @param string $format Format to use for the video
*
* @return string Filename of extracted video
* */
public function getFilename($url, $format = null)
{
$this->procBuilder->setArguments(
array(
'--get-filename',
$url
)
);
if (isset($format)) {
$this->procBuilder->add('-f '.$format);
}
$process = $this->procBuilder->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new \Exception($process->getErrorOutput());
} else {
return trim($process->getOutput());
}
}
/**
* Get filename of audio from URL of page
*
* @param string $url URL of page
* @param string $format Format to use for the video
*
* @return string Filename of converted audio file
* */
public function getAudioFilename($url, $format = null)
{
return html_entity_decode(
pathinfo(
$this->getFilename($url, $format),
PATHINFO_FILENAME
).'.mp3',
ENT_COMPAT,
'ISO-8859-1'
);
}
/**
* Get audio stream of converted video
*
* @param string $url URL of page
* @param string $format Format to use for the video
*
* @return resource popen stream
*/
public function getAudioStream($url, $format)
{
if (!shell_exec('which '.$this->config->avconv)) {
throw(new \Exception('Can\'t find avconv or ffmpeg'));
}
$video = $this->getJSON($url, $format);
//Vimeo needs a correct user-agent
ini_set(
'user_agent',
$video->http_headers->{'User-Agent'}
);
$avconvProc = ProcessBuilder::create(
array(
$this->config->avconv,
'-v', 'quiet',
'-i', '-',
'-f', 'mp3',
'-vn',
'pipe:1'
)
);
if (parse_url($video->url, PHP_URL_SCHEME) == 'rtmp') {
if (!shell_exec('which '.$this->config->rtmpdump)) {
throw(new \Exception('Can\'t find rtmpdump'));
}
$builder = new ProcessBuilder(
array(
$this->config->rtmpdump,
'-q',
'-r',
$video->url,
'--pageUrl', $video->webpage_url
)
);
if (isset($video->player_url)) {
$builder->add('--swfVfy');
$builder->add($video->player_url);
}
if (isset($video->flash_version)) {
$builder->add('--flashVer');
$builder->add($video->flash_version);
}
if (isset($video->play_path)) {
$builder->add('--playpath');
$builder->add($video->play_path);
}
if (isset($video->rtmp_conn)) {
foreach ($video->rtmp_conn as $conn) {
$builder->add('--conn');
$builder->add($conn);
}
}
if (isset($video->app)) {
$builder->add('--app');
$builder->add($video->app);
}
$chain = new Chain($builder->getProcess());
$chain->add('|', $avconvProc);
} else {
if (!shell_exec('which '.$this->config->curl)) {
throw(new \Exception('Can\'t find curl'));
}
$chain = new Chain(
ProcessBuilder::create(
array_merge(
array(
$this->config->curl,
'--silent',
'--location',
'--user-agent', $video->http_headers->{'User-Agent'},
$video->url
),
$this->config->curl_params
)
)
);
$chain->add('|', $avconvProc);
}
return popen($chain->getProcess()->getCommandLine(), 'r');
} }
} }

View file

@ -1,74 +1,82 @@
{ {
"name": "rudloff/alltube", "name": "rudloff/alltube",
"description": "HTML GUI for youtube-dl", "description": "HTML GUI for youtube-dl",
"license": "GPL-3.0", "license": "GPL-3.0",
"homepage": "http://alltubedownload.net/", "homepage": "http://alltubedownload.net/",
"type": "project", "type": "project",
"require": { "require": {
"smarty/smarty": "~3.1.29", "smarty/smarty": "~3.1.29",
"rg3/youtube-dl": "~2016.07.26.2", "rg3/youtube-dl": "~2016.08.12",
"slim/slim": "~3.5.0", "slim/slim": "~3.5.0",
"mathmarques/smarty-view": "~1.1.0", "mathmarques/smarty-view": "~1.1.0",
"symfony/yaml": "~3.1.0", "symfony/yaml": "~3.1.0",
"symfony/process": "~3.1.0", "symfony/process": "~3.1.0",
"ptachoire/process-builder-chain": "~1.2.0", "ptachoire/process-builder-chain": "~1.2.0",
"ffmpeg/ffmpeg": "dev-release", "ffmpeg/ffmpeg": "dev-release",
"rudloff/smarty-plugin-noscheme": "~0.1.0", "rudloff/smarty-plugin-noscheme": "~0.1.0",
"rudloff/rtmpdump-bin": "~2.3" "rudloff/rtmpdump-bin": "~2.3"
}, },
"require-dev": { "require-dev": {
"symfony/var-dumper": "~3.1.0" "symfony/var-dumper": "~3.1.0",
}, "squizlabs/php_codesniffer": "~2.6.2",
"extra": { "phpunit/phpunit": "~5.5.2"
"paas": { },
"nginx-includes": [ "extra": {
"nginx.conf" "paas": {
] "nginx-includes": [
} "nginx.conf"
}, ]
"repositories": [{ }
"type": "package", },
"package": { "repositories": [
"name": "rg3/youtube-dl", {
"version": "2016.07.26.2", "type": "package",
"source": { "package": {
"url": "https://github.com/rg3/youtube-dl.git", "name": "rg3/youtube-dl",
"type": "git", "version": "2016.08.12",
"reference": "2016.07.26.2" "source": {
} "url": "https://github.com/rg3/youtube-dl.git",
} "type": "git",
}, { "reference": "2016.08.12"
"type": "package", }
"package": { }
"name": "ffmpeg/ffmpeg", },
"version": "dev-release", {
"dist": { "type": "package",
"url": "http://johnvansickle.com/ffmpeg/releases/ffmpeg-release-64bit-static.tar.xz", "package": {
"type": "xz" "name": "ffmpeg/ffmpeg",
}, "version": "dev-release",
"bin": [ "dist": {
"ffmpeg" "url": "http://johnvansickle.com/ffmpeg/releases/ffmpeg-release-64bit-static.tar.xz",
] "type": "xz"
} },
}], "bin": [
"authors": [{ "ffmpeg"
"name": "Pierre Rudloff", ]
"email": "contact@rudloff.pro", }
"homepage": "https://rudloff.pro/", }
"role": "Developer" ],
}, { "authors": [
"name": "Olivier Haquette", {
"email": "contact@olivierhaquette.fr", "name": "Pierre Rudloff",
"homepage": "http://olivierhaquette.fr/", "email": "contact@rudloff.pro",
"role": "Designer" "homepage": "https://rudloff.pro/",
}], "role": "Developer"
"autoload": { },
"psr-4": { {
"Alltube\\": "classes/", "name": "Olivier Haquette",
"Alltube\\Controller\\": "controllers/" "email": "contact@olivierhaquette.fr",
} "homepage": "http://olivierhaquette.fr/",
}, "role": "Designer"
"config": { }
"secure-http": false ],
} "autoload": {
"psr-4": {
"Alltube\\": "classes/",
"Alltube\\Controller\\": "controllers/"
}
},
"config": {
"secure-http": false
}
} }

1385
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -10,3 +10,4 @@ curl_params:
convert: false convert: false
avconv: vendor/bin/ffmpeg avconv: vendor/bin/ffmpeg
rtmpdump: vendor/bin/rtmpdump rtmpdump: vendor/bin/rtmpdump
curl: /usr/bin/curl

1
config_test.yml Normal file
View file

@ -0,0 +1 @@
convert: false

View file

@ -1,39 +1,44 @@
<?php <?php
/** /**
* FrontController class * FrontController class
* */
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
namespace Alltube\Controller; namespace Alltube\Controller;
use Alltube\VideoDownload; use Alltube\VideoDownload;
use Alltube\Config; use Alltube\Config;
use Symfony\Component\Process\ProcessBuilder;
use Chain\Chain;
use Slim\Http\Stream; use Slim\Http\Stream;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use Slim\Container;
/** /**
* Main controller * Main controller
* */
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
class FrontController class FrontController
{ {
public function __construct($container) /**
* Config instance
* @var Config
*/
private $config;
/**
* VideoDownload instance
* @var VideoDownload
*/
private $download;
/**
* Slim dependency container
* @var Container
*/
private $container;
/**
* FrontController constructor
* @param Container $container Slim dependency container
*/
public function __construct(Container $container)
{ {
$this->config = Config::getInstance(); $this->config = Config::getInstance();
$this->download = new VideoDownload(); $this->download = new VideoDownload();
@ -52,24 +57,13 @@ class FrontController
{ {
$this->container->view->render( $this->container->view->render(
$response, $response,
'head.tpl', 'index.tpl',
array( array(
'convert'=>$this->config->convert,
'class'=>'index', 'class'=>'index',
'description'=>'Easily download videos from Youtube, Dailymotion, Vimeo and other websites.' 'description'=>'Easily download videos from Youtube, Dailymotion, Vimeo and other websites.'
) )
); );
$this->container->view->render(
$response,
'header.tpl'
);
$this->container->view->render(
$response,
'index.tpl',
array(
'convert'=>$this->config->convert
)
);
$this->container->view->render($response, 'footer.tpl');
} }
/** /**
@ -84,24 +78,15 @@ class FrontController
{ {
$this->container->view->render( $this->container->view->render(
$response, $response,
'head.tpl', 'extractors.tpl',
array( array(
'extractors'=>$this->download->listExtractors(),
'class'=>'extractors', 'class'=>'extractors',
'title'=>'Supported websites', 'title'=>'Supported websites',
'description' 'description'
=>'List of all supported websites from which Alltube Download can extract video or audio files' =>'List of all supported websites from which Alltube Download can extract video or audio files'
) )
); );
$this->container->view->render($response, 'header.tpl');
$this->container->view->render($response, 'logo.tpl');
$this->container->view->render(
$response,
'extractors.tpl',
array(
'extractors'=>$this->download->listExtractors()
)
);
$this->container->view->render($response, 'footer.tpl');
} }
/** /**
@ -110,7 +95,7 @@ class FrontController
* @param Request $request PSR-7 request * @param Request $request PSR-7 request
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return void * @return Response HTTP response
*/ */
public function video(Request $request, Response $response) public function video(Request $request, Response $response)
{ {
@ -122,95 +107,16 @@ class FrontController
$url = $this->download->getURL($params["url"], 'mp3[protocol^=http]'); $url = $this->download->getURL($params["url"], 'mp3[protocol^=http]');
return $response->withRedirect($url); return $response->withRedirect($url);
} catch (\Exception $e) { } catch (\Exception $e) {
$video = $this->download->getJSON($params["url"], 'bestaudio/best');
if (!shell_exec('which '.$this->config->avconv)) {
throw(new \Exception('Can\'t find avconv or ffmpeg'));
}
$avconvProc = ProcessBuilder::create(
array(
$this->config->avconv,
'-v', 'quiet',
'-i', '-',
'-f', 'mp3',
'-vn',
'pipe:1'
)
);
//Vimeo needs a correct user-agent
ini_set(
'user_agent',
$video->http_headers->{'User-Agent'}
);
$response = $response->withHeader( $response = $response->withHeader(
'Content-Disposition', 'Content-Disposition',
'attachment; filename="'. 'attachment; filename="'.
html_entity_decode( $this->download->getAudioFilename($params["url"], 'bestaudio/best').'"'
pathinfo(
$video->_filename,
PATHINFO_FILENAME
).'.mp3',
ENT_COMPAT,
'ISO-8859-1'
).'"'
); );
$response = $response->withHeader('Content-Type', 'audio/mpeg'); $response = $response->withHeader('Content-Type', 'audio/mpeg');
if (parse_url($video->url, PHP_URL_SCHEME) == 'rtmp') {
if (!shell_exec('which '.$this->config->rtmpdump)) {
throw(new \Exception('Can\'t find rtmpdump'));
}
$builder = new ProcessBuilder(
array(
$this->config->rtmpdump,
'-q',
'-r',
$video->url,
'--pageUrl', $video->webpage_url
)
);
if (isset($video->player_url)) {
$builder->add('--swfVfy');
$builder->add($video->player_url);
}
if (isset($video->flash_version)) {
$builder->add('--flashVer');
$builder->add($video->flash_version);
}
if (isset($video->play_path)) {
$builder->add('--playpath');
$builder->add($video->play_path);
}
foreach ($video->rtmp_conn as $conn) {
$builder->add('--conn');
$builder->add($conn);
}
$chain = new Chain($builder->getProcess());
$chain->add('|', $avconvProc);
} else {
if (!shell_exec('which curl')) {
throw(new \Exception('Can\'t find curl'));
}
$chain = new Chain(
ProcessBuilder::create(
array_merge(
array(
'curl',
'--silent',
'--user-agent', $video->http_headers->{'User-Agent'},
$video->url
),
$this->config->curl_params
)
)
);
$chain->add('|', $avconvProc);
}
if ($request->isGet()) { if ($request->isGet()) {
$response = $response->withBody(new Stream(popen($chain->getProcess()->getCommandLine(), 'r'))); $process = $this->download->getAudioStream($params["url"], 'bestaudio/best');
$response = $response->withBody(new Stream($process));
} }
return $response; return $response;
} }
@ -218,45 +124,38 @@ class FrontController
$video = $this->download->getJSON($params["url"]); $video = $this->download->getJSON($params["url"]);
$this->container->view->render( $this->container->view->render(
$response, $response,
'head.tpl', 'video.tpl',
array( array(
'video'=>$video,
'class'=>'video', 'class'=>'video',
'title'=>$video->title, 'title'=>$video->title,
'description'=>'Download "'.$video->title.'" from '.$video->extractor_key 'description'=>'Download "'.$video->title.'" from '.$video->extractor_key
) )
); );
$this->container->view->render(
$response,
'video.tpl',
array(
'video'=>$video
)
);
$this->container->view->render($response, 'footer.tpl');
} }
} else { } else {
return $response->withRedirect($this->container->get('router')->pathFor('index')); return $response->withRedirect($this->container->get('router')->pathFor('index'));
} }
} }
/**
* Display an error page
* @param Request $request PSR-7 request
* @param Response $response PSR-7 response
* @param \Exception $exception Error to display
* @return Response HTTP response
*/
public function error(Request $request, Response $response, \Exception $exception) public function error(Request $request, Response $response, \Exception $exception)
{ {
$this->container->view->render( $this->container->view->render(
$response, $response,
'head.tpl', 'error.tpl',
array( array(
'errors'=>$exception->getMessage(),
'class'=>'video', 'class'=>'video',
'title'=>'Error' 'title'=>'Error'
) )
); );
$this->container->view->render(
$response,
'error.tpl',
array(
'errors'=>$exception->getMessage()
)
);
$this->container->view->render($response, 'footer.tpl');
return $response->withStatus(500); return $response->withStatus(500);
} }
@ -266,7 +165,7 @@ class FrontController
* @param Request $request PSR-7 request * @param Request $request PSR-7 request
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return void * @return Response HTTP response
*/ */
public function redirect(Request $request, Response $response) public function redirect(Request $request, Response $response)
{ {
@ -288,7 +187,7 @@ class FrontController
* @param Request $request PSR-7 request * @param Request $request PSR-7 request
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return void * @return Response HTTP response
*/ */
public function json(Request $request, Response $response) public function json(Request $request, Response $response)
{ {

View file

@ -1,17 +1,4 @@
<?php <?php
/**
* PHP web interface for youtube-dl (http://rg3.github.com/youtube-dl/)
* Index page
*
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @author Olivier Haquette <contact@olivierhaquette.fr>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
require_once __DIR__.'/vendor/autoload.php'; require_once __DIR__.'/vendor/autoload.php';
use Alltube\VideoDownload; use Alltube\VideoDownload;
use Alltube\Controller\FrontController; use Alltube\Controller\FrontController;

View file

@ -1,31 +1,37 @@
{ {
"short_name": "AllTube", "short_name": "AllTube",
"name": "AllTube Download", "name": "AllTube Download",
"description": "Easily download videos from Youtube, Dailymotion, Vimeo and other websites", "description": "Easily download videos from Youtube, Dailymotion, Vimeo and other websites",
"display": "standalone", "display": "standalone",
"icons": [{ "icons": [
"src": "img/favicon.png", {
"sizes": "32x32", "src": "img/favicon.png",
"type": "image/png" "sizes": "32x32",
}, { "type": "image/png"
"src": "img/logo_60.png", },
"sizes": "60x60", {
"type": "image/png" "src": "img/logo_60.png",
}, { "sizes": "60x60",
"src": "img/logo_90.png", "type": "image/png"
"sizes": "90x60", },
"type": "image/png" {
}, { "src": "img/logo_90.png",
"src": "img/logo_app.png", "sizes": "90x60",
"sizes": "243x243", "type": "image/png"
"type": "image/png" },
}, { {
"src": "img/logo_250.png", "src": "img/logo_app.png",
"sizes": "250x250", "sizes": "243x243",
"type": "image/png" "type": "image/png"
}], },
"lang": "en", {
"start_url": "./", "src": "img/logo_250.png",
"theme_color": "#4F4F4F", "sizes": "250x250",
"orientation": "portrait" "type": "image/png"
}
],
"lang": "en",
"start_url": "./",
"theme_color": "#4F4F4F",
"orientation": "portrait"
} }

View file

@ -1,17 +1,17 @@
{ {
"name": "AllTube", "name": "AllTube",
"description": "Easily download videos from Youtube, Dailymotion, Vimeo and other websites", "description": "Easily download videos from Youtube, Dailymotion, Vimeo and other websites",
"developer": { "developer": {
"name": "Pierre Rudloff", "name": "Pierre Rudloff",
"url": "https://rudloff.pro/" "url": "https://rudloff.pro/"
}, },
"icons": { "icons": {
"32": "/img/favicon.png", "32": "/img/favicon.png",
"60": "/img/logo_60.png", "60": "/img/logo_60.png",
"90": "/img/logo_90.png", "90": "/img/logo_90.png",
"243": "/img/logo_app.png", "243": "/img/logo_app.png",
"250": "/img/logo_250.png" "250": "/img/logo_250.png"
}, },
"default_locale": "en", "default_locale": "en",
"launch_path": "/index.php" "launch_path": "/index.php"
} }

View file

@ -1,25 +1,41 @@
{ {
"name": "alltube", "name": "alltube",
"version": "0.5.0", "description": "HTML GUI for youtube-dl",
"license": "GPL-3.0", "version": "0.5.0",
"dependencies": { "author": "Pierre Rudloff",
"grunt": "~1.0.1", "bugs": "https://github.com/Rudloff/alltube/issues",
"grunt-cli": "~1.2.0", "dependencies": {
"grunt-contrib-cssmin": "~1.0.0", "bower": "~1.7.1",
"grunt-contrib-uglify": "~1.0.0", "grunt": "~1.0.1",
"grunt-contrib-watch": "~1.0.0", "grunt-cli": "~1.2.0",
"grunt-phpcs": "~0.4.0", "grunt-contrib-cssmin": "~1.0.0",
"grunt-phpunit": "~0.3.6", "grunt-contrib-uglify": "~2.0.0"
"grunt-contrib-compress": "~1.3.0", },
"bower": "~1.7.1", "devDependencies": {
"grunt-githash": "~0.1.3", "grunt-contrib-compress": "~1.3.0",
"grunt-jslint": "~1.1.14" "grunt-contrib-watch": "~1.0.0",
}, "grunt-fixpack": "~0.1.0",
"repository": { "grunt-githash": "~0.1.3",
"type": "git", "grunt-jslint": "~1.1.14",
"url": "https://github.com/Rudloff/alltube.git" "grunt-jsonlint": "~1.1.0",
}, "grunt-phpcs": "~0.4.0",
"scripts": { "grunt-phpdocumentor": "~0.4.1",
"postinstall": "node node_modules/bower/bin/bower install && node node_modules/grunt-cli/bin/grunt" "grunt-phpunit": "~0.3.6"
} },
"homepage": "https://www.alltubedownload.net/",
"keywords": [
"alltube",
"dowload",
"video",
"youtube"
],
"license": "GPL-3.0",
"main": "index.php",
"repository": {
"type": "git",
"url": "https://github.com/Rudloff/alltube.git"
},
"scripts": {
"postinstall": "node node_modules/bower/bin/bower install && node node_modules/grunt-cli/bin/grunt"
}
} }

View file

@ -11,5 +11,6 @@
</testsuites> </testsuites>
<logging> <logging>
<log type="coverage-html" target="coverage/" /> <log type="coverage-html" target="coverage/" />
<log type="coverage-clover" target="clover.xml" />
</logging> </logging>
</phpunit> </phpunit>

View file

@ -1,6 +1,7 @@
{include file='inc/head.tpl'}
<div class="wrapper"> <div class="wrapper">
<div class="main error"> <div class="main error">
{include file="logo.tpl"} {include file="inc/logo.tpl"}
<h2>An error occured</h2> <h2>An error occured</h2>
Please check the URL of your video. Please check the URL of your video.
<p><i> <p><i>
@ -9,4 +10,5 @@
<br/> <br/>
{/foreach} {/foreach}
</i></p> </i></p>
</div> </div>
{include file='inc/footer.tpl'}

View file

@ -1,8 +1,12 @@
{include file='inc/head.tpl'}
{include file='inc/header.tpl'}
{include file='inc/logo.tpl'}
<h2 class="titre">Supported websites</h2> <h2 class="titre">Supported websites</h2>
<div class="tripleliste"> <div class="tripleliste">
<ul> <ul>
{foreach $extractors as $extractor} {foreach $extractors as $extractor}
<li>{$extractor}</li> <li>{$extractor}</li>
{/foreach} {/foreach}
</ul> </ul>
</div> </div>
{include file='inc/footer.tpl'}

View file

@ -1,4 +1,6 @@
<div class="main"> {include file='inc/head.tpl'}
{include file='inc/header.tpl'}
<div class="main">
<div><img class="logo" src="{base_url|noscheme}/img/logo.png" <div><img class="logo" src="{base_url|noscheme}/img/logo.png"
alt="AllTube Download" width="328" height="284"></div> alt="AllTube Download" width="328" height="284"></div>
<form action="{path_for name="video"}"> <form action="{path_for name="video"}">
@ -27,3 +29,4 @@
</div> </div>
</div> </div>
{include file='inc/footer.tpl'}

View file

@ -1,7 +1,8 @@
{include file="inc/head.tpl"}
<div class="wrapper"> <div class="wrapper">
<div itemscope itemtype="http://schema.org/VideoObject"> <div itemscope itemtype="http://schema.org/VideoObject">
<div class="main"> <div class="main">
{include file="logo.tpl"} {include file="inc/logo.tpl"}
<p id="download_intro">You are going to download<i itemprop="name"> <p id="download_intro">You are going to download<i itemprop="name">
<a itemprop="url" id="video_link" <a itemprop="url" id="video_link"
data-ext="{$video->ext}" data-ext="{$video->ext}"
@ -86,3 +87,4 @@
{/if} {/if}
</div> </div>
</div> </div>
{include file="inc/footer.tpl"}

View file

@ -1,42 +1,45 @@
<?php <?php
/** /**
* ConfigTest class * ConfigTest class
* */
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Config; use Alltube\Config;
/** /**
* Unit tests for the Config class * Unit tests for the Config class
* */
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
class ConfigTest extends \PHPUnit_Framework_TestCase class ConfigTest extends \PHPUnit_Framework_TestCase
{ {
private $config;
protected function setUp()
{
$this->config = Config::getInstance('config_test.yml');
}
/** /**
* Test the getInstance function * Test the getInstance function
* *
* @return void * @return void
*/ */
public function testGetInstance() public function testGetInstance()
{
$this->assertEquals($this->config->convert, false);
$this->assertInternalType('array', $this->config->curl_params);
$this->assertInternalType('array', $this->config->params);
$this->assertInternalType('string', $this->config->youtubedl);
$this->assertInternalType('string', $this->config->python);
$this->assertInternalType('string', $this->config->avconv);
$this->assertInternalType('string', $this->config->rtmpdump);
}
public function testGetInstanceWithEnv()
{ {
putenv('CONVERT=1'); putenv('CONVERT=1');
$config = Config::getInstance(); Config::destroyInstance();
$config = Config::getInstance('config_test.yml');
$this->assertEquals($config->convert, true); $this->assertEquals($config->convert, true);
} }
} }

View file

@ -1,37 +1,38 @@
<?php <?php
/** /**
* VideoDownloadTest class * VideoDownloadTest class
* */
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\VideoDownload; use Alltube\VideoDownload;
/** /**
* Unit tests for the VideoDownload class * Unit tests for the VideoDownload class
* */
* PHP Version 5.3.10
*
* @category Youtube-dl
* @package Youtubedl
* @author Pierre Rudloff <contact@rudloff.pro>
* @license GNU General Public License http://www.gnu.org/licenses/gpl.html
* @link http://rudloff.pro
* */
class VideoDownloadTest extends \PHPUnit_Framework_TestCase class VideoDownloadTest extends \PHPUnit_Framework_TestCase
{ {
/**
* VideoDownload instance
* @var VideoDownload
*/
private $download;
/**
* Initialize properties used by test
*/
protected function setUp() protected function setUp()
{ {
$this->download = new VideoDownload(); $this->download = new VideoDownload();
} }
/**
* Destroy properties after test
*/
protected function tearDown()
{
\Alltube\Config::destroyInstance();
}
/** /**
* Test listExtractors function * Test listExtractors function
* *
@ -48,6 +49,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
* *
* @param string $url URL * @param string $url URL
* @param string $format Format * @param string $format Format
* @param string $filename Filename
* @param string $domain Domain
* *
* @return void * @return void
* @dataProvider urlProvider * @dataProvider urlProvider
@ -75,7 +78,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
/** /**
* Provides URLs for tests * Provides URLs for tests
* *
* @return array * @return array[]
*/ */
public function urlProvider() public function urlProvider()
{ {
@ -83,26 +86,42 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
array( array(
'https://www.youtube.com/watch?v=M7IpKCZ47pU', null, 'https://www.youtube.com/watch?v=M7IpKCZ47pU', null,
"It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU.mp4", "It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU.mp4",
'googlevideo.com' 'googlevideo.com',
"It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU.mp3"
), ),
array( array(
'https://www.youtube.com/watch?v=RJJ6FCAXvKg', 22, 'https://www.youtube.com/watch?v=RJJ6FCAXvKg', 22,
"'Heart Attack' - Demi Lovato ". "'Heart Attack' - Demi Lovato ".
"(Sam Tsui & Against The Current)-RJJ6FCAXvKg.mp4", "(Sam Tsui & Against The Current)-RJJ6FCAXvKg.mp4",
'googlevideo.com' 'googlevideo.com',
"'Heart Attack' - Demi Lovato ".
"(Sam Tsui & Against The Current)-RJJ6FCAXvKg.mp3"
), ),
array( array(
'https://vimeo.com/24195442', null, 'https://vimeo.com/24195442', null,
"Carving the Mountains-24195442.mp4", "Carving the Mountains-24195442.mp4",
'vimeocdn.com' 'vimeocdn.com',
"Carving the Mountains-24195442.mp3"
), ),
array(
'http://www.bbc.co.uk/programmes/b039g8p7', 'bestaudio/best',
"Leonard Cohen, Kaleidoscope - BBC Radio 4-b039d07m.flv",
'bbcodspdns.fcod.llnwd.net',
"Leonard Cohen, Kaleidoscope - BBC Radio 4-b039d07m.mp3"
),
array(
'http://www.rtl2.de/sendung/grip-das-motormagazin/folge/folge-203-0', 'bestaudio/best',
"GRIP sucht den Sommerkönig-folge-203-0.f4v",
'edgefcs.net',
"GRIP sucht den Sommerkönig-folge-203-0.mp3"
)
); );
} }
/** /**
* Provides incorrect URLs for tests * Provides incorrect URLs for tests
* *
* @return array * @return array[]
*/ */
public function errorUrlProvider() public function errorUrlProvider()
{ {
@ -144,4 +163,103 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
{ {
$videoURL = $this->download->getJSON($url); $videoURL = $this->download->getJSON($url);
} }
/**
* Test getFilename function
*
* @param string $url URL
* @param string $format Format
* @param string $filename Filename
*
* @return void
* @dataProvider urlProvider
*/
public function testGetFilename($url, $format, $filename)
{
$videoFilename = $this->download->getFilename($url, $format);
$this->assertEquals($videoFilename, $filename);
}
/**
* Test getFilename function errors
*
* @param string $url URL
*
* @return void
* @expectedException Exception
* @dataProvider ErrorUrlProvider
*/
public function testGetFilenameError($url)
{
$this->download->getFilename($url);
}
/**
* Test getAudioFilename function
*
* @param string $url URL
* @param string $format Format
* @param string $filename Filename
* @param string $domain Domain
* @param string $audioFilename MP3 audio file name
*
* @return void
* @dataProvider urlProvider
*/
public function testGetAudioFilename($url, $format, $filename, $domain, $audioFilename)
{
$videoFilename = $this->download->getAudioFilename($url, $format);
$this->assertEquals($videoFilename, $audioFilename);
}
/**
* Test getAudioStream function
*
* @param string $url URL
* @param string $format Format
*
* @return void
* @dataProvider urlProvider
*/
public function testGetAudioStream($url, $format)
{
$stream = $this->download->getAudioStream($url, $format);
$this->assertInternalType('resource', $stream);
$this->assertFalse(feof($stream));
}
/**
* Test getAudioStream function without avconv
*
* @param string $url URL
* @param string $format Format
*
* @return void
* @expectedException Exception
* @dataProvider urlProvider
*/
public function testGetAudioStreamAvconvError($url, $format)
{
$config = \Alltube\Config::getInstance();
$config->avconv = 'foobar';
$this->download->getAudioStream($url, $format);
}
/**
* Test getAudioStream function without curl or rtmpdump
*
* @param string $url URL
* @param string $format Format
*
* @return void
* @expectedException Exception
* @dataProvider urlProvider
*/
public function testGetAudioStreamCurlError($url, $format)
{
$config = \Alltube\Config::getInstance();
$config->curl = 'foobar';
$config->rtmpdump = 'foobar';
$this->download->getAudioStream($url, $format);
}
} }