diff --git a/.travis.yml b/.travis.yml index dfeb0ad..2281c97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ install: - composer install before_install: - npm install -g npm@3 +script: vendor/bin/phpunit after_success: - bash <(curl -s https://codecov.io/bash) before_deploy: diff --git a/Dockerfile b/Dockerfile index 92426ba..211291d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,12 +5,12 @@ RUN apt-get install -y libicu-dev xz-utils git zlib1g-dev python nodejs RUN docker-php-ext-install mbstring RUN docker-php-ext-install intl RUN docker-php-ext-install zip -RUN npm install -g bower grunt-cli RUN a2enmod rewrite RUN curl -sS https://getcomposer.org/installer | php COPY php.ini /usr/local/etc/php/ COPY . /var/www/html/ RUN php composer.phar install --prefer-dist RUN npm install -RUN bower --allow-root install -RUN grunt +RUN ./node_modules/.bin/bower --allow-root install +RUN ./node_modules/.bin/grunt +ENV CONVERT=1 diff --git a/FAQ.md b/FAQ.md index 6331183..c03462e 100644 --- a/FAQ.md +++ b/FAQ.md @@ -12,15 +12,15 @@ You can ususally download the video by doing *File > Save to* or *ctrl + S*. 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 +* `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. +See [`config.example.yml`](config.example.yml) for default values. ## How do I enable audio conversion? @@ -61,7 +61,7 @@ Some websites generate an unique video URL for each IP address. When using Alltu There are two known workarounds: * You can run Alltube locally on your computer. -* You can use the experimental `feature/stream` branch which streams the video through the server in order to bypass IP restrictions. +* You can enable streaming videos through the server (see below). Please note that this can use a lot of resources on the server (which is why we won't enable it on alltubedownload.net). ## CSS and JavaScript files are missing @@ -71,3 +71,61 @@ You need to either: * Use a [release package](https://github.com/Rudloff/alltube/releases) * Run `npm install` (see detailed instructions in the [README](README.md#from-git)) + +## I get a 404 error on every page except the index + +This is probably because your server does not have mod_rewrite or AllowOverride is disabled. +You can work around this by adding this to your `config.yml` file: + +```yaml +uglyUrls: true +``` + +## How do I enable streaming videos through the server? + +You need to add this to your `config.yml` file: + +```yaml +stream: true +``` + +Note that this can use a lot of ressources on your server. + +## I want to download M3U videos + +You need to enable streaming (see above). + +## The downloaded videos have a strange name like `videoplayback.mp4` + +Alltube can rename videos automatically if you enable streaming (see above). + +## I want to download a video that isn't available in my country + +If the video is available in the server's country, you can download it if you enable streaming (see above). + +## How do I run the Docker image? + +```bash +docker run -p 8080:80 rudloff/alltube +``` + +## How do I run Heroku locally? + +You should be able to use `heroku local` like this: + +```bash +sudo APACHE_LOCK_DIR=. APACHE_PID_FILE=./pid APACHE_RUN_USER=www-data APACHE_RUN_GROUP=www-data APACHE_LOG_DIR=. heroku local +``` + +You might need to create some symlinks before that: + +```bash +ln -s /usr/sbin/apache2 /usr/sbin/httpd +ln -s /usr/sbin/php-fpm7.0 /usr/sbin/php-fpm +``` + +And you probably need to run this in another terminal after `heroku local` has finished launching `php-fpm`: + +```bash +chmod 0667 /tmp/heroku.fcgi.5000.sock +``` diff --git a/README.md b/README.md index f35d40b..5a3c08f 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,16 @@ chmod 777 templates_c/ If your web server is Apache, you need to set the `AllowOverride` setting to `All` or `FileInfo`. +#### Update + +When updating from Git, you need to run npm and Composer again: + +```bash +git pull +npm install +composer install +``` + ## Config If you want to use a custom config, you need to create a config file: diff --git a/classes/Config.php b/classes/Config.php index a4ff93d..0c3fde3 100644 --- a/classes/Config.php +++ b/classes/Config.php @@ -38,7 +38,7 @@ class Config * * @var array */ - public $params = ['--no-playlist', '--no-warnings', '-f best[protocol^=http]', '--playlist-end', 1]; + public $params = ['--no-playlist', '--no-warnings', '--playlist-end', 1]; /** * Enable audio conversion. @@ -82,6 +82,13 @@ class Config */ public $uglyUrls = false; + /** + * Stream downloaded files trough server? + * + * @var bool + */ + public $stream = false; + /** * YAML config file path. * diff --git a/classes/VideoDownload.php b/classes/VideoDownload.php index 39230c9..345d2eb 100644 --- a/classes/VideoDownload.php +++ b/classes/VideoDownload.php @@ -295,4 +295,33 @@ class VideoDownload return popen($chain->getProcess()->getCommandLine(), 'r'); } + + /** + * Get video stream from an M3U playlist. + * + * @param \stdClass $video Video object returned by getJSON + * + * @return resource popen stream + */ + public function getM3uStream(\stdClass $video) + { + if (!shell_exec('which '.$this->config->avconv)) { + throw(new \Exception('Can\'t find avconv or ffmpeg')); + } + + $procBuilder = ProcessBuilder::create( + [ + $this->config->avconv, + '-v', 'quiet', + '-i', $video->url, + '-f', $video->ext, + '-c', 'copy', + '-bsf:a', 'aac_adtstoasc', + '-movflags', 'frag_keyframe+empty_moov', + 'pipe:1', + ] + ); + + return popen($procBuilder->getProcess()->getCommandLine(), 'r'); + } } diff --git a/composer.json b/composer.json index 3d91719..fb072d0 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,8 @@ "symfony/process": "~3.2.0", "ptachoire/process-builder-chain": "~1.2.0", "rudloff/smarty-plugin-noscheme": "~0.1.0", + "guzzlehttp/guzzle": "~6.2.0", + "rudloff/rtmpdump-bin": "~2.3", "aura/session": "~2.1.0" }, "require-dev": { @@ -19,7 +21,7 @@ "squizlabs/php_codesniffer": "~2.7.0", "phpunit/phpunit": "~5.7.2", "ffmpeg/ffmpeg": "dev-release", - "rg3/youtube-dl": "~2017.01.10", + "rg3/youtube-dl": "~2017.03.07", "rudloff/rtmpdump-bin": "~2.3", "heroku/heroku-buildpack-php": "*" }, @@ -35,10 +37,10 @@ "type": "package", "package": { "name": "rg3/youtube-dl", - "version": "2017.01.10", + "version": "2017.03.07", "dist": { "type": "zip", - "url": "https://github.com/rg3/youtube-dl/archive/2017.01.10.zip" + "url": "https://github.com/rg3/youtube-dl/archive/2017.03.07.zip" } } }, @@ -48,7 +50,7 @@ "name": "ffmpeg/ffmpeg", "version": "dev-release", "dist": { - "url": "http://johnvansickle.com/ffmpeg/releases/ffmpeg-release-64bit-static.tar.xz", + "url": "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-64bit-static.tar.xz", "type": "xz" }, "bin": [ @@ -77,9 +79,6 @@ "Alltube\\Controller\\": "controllers/" } }, - "config": { - "secure-http": false - }, "scripts": { "compile": "composer install --dev" } diff --git a/composer.lock b/composer.lock index f9f96e3..8edfbe1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "16268cc5135ed42aedf75446f4e39bd7", + "content-hash": "1ac742cf7c2832a7af628c157ced2dbc", "packages": [ { "name": "aura/session", @@ -70,18 +70,21 @@ }, { "name": "container-interop/container-interop", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/container-interop/container-interop.git", - "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e" + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e", - "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", "shasum": "" }, + "require": { + "psr/container": "^1.0" + }, "type": "library", "autoload": { "psr-4": { @@ -93,7 +96,186 @@ "MIT" ], "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", - "time": "2014-12-30T15:22:37+00:00" + "homepage": "https://github.com/container-interop/container-interop", + "time": "2017-02-14T19:40:03+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.2.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/8d6c6cc55186db87b7dc5009827429ba4e9dc006", + "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0", + "psr/log": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2017-02-28T22:50:30+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "0d6c7ca039329247e4f0f8f8f6506810e8248855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/0d6c7ca039329247e4f0f8f8f6506810e8248855", + "reference": "0d6c7ca039329247e4f0f8f8f6506810e8248855", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-02-27T10:51:17+00:00" }, { "name": "jeremykendall/php-domain-parser", @@ -273,16 +455,16 @@ }, { "name": "nikic/fast-route", - "version": "v1.1.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/nikic/FastRoute.git", - "reference": "f3dcf5130e634b6123d40727d612ec6aa4f61fb3" + "reference": "b5f95749071c82a8e0f58586987627054400cdf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/f3dcf5130e634b6123d40727d612ec6aa4f61fb3", - "reference": "f3dcf5130e634b6123d40727d612ec6aa4f61fb3", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/b5f95749071c82a8e0f58586987627054400cdf6", + "reference": "b5f95749071c82a8e0f58586987627054400cdf6", "shasum": "" }, "require": { @@ -312,7 +494,7 @@ "router", "routing" ], - "time": "2016-10-20T17:36:47+00:00" + "time": "2017-01-19T11:35:12+00:00" }, { "name": "pimple/pimple", @@ -360,6 +542,55 @@ ], "time": "2015-09-11T15:10:35+00:00" }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -446,6 +677,34 @@ "description": "Add ability to chain symfony processes", "time": "2016-04-10T08:33:20+00:00" }, + { + "name": "rudloff/rtmpdump-bin", + "version": "2.3", + "source": { + "type": "git", + "url": "https://github.com/Rudloff/rtmpdump-bin.git", + "reference": "133cdd80e3bab66593e88a5276158596383afd97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Rudloff/rtmpdump-bin/zipball/133cdd80e3bab66593e88a5276158596383afd97", + "reference": "133cdd80e3bab66593e88a5276158596383afd97", + "shasum": "" + }, + "require-dev": { + "rtmpdump/rtmpdump": "2.3" + }, + "bin": [ + "rtmpdump" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0" + ], + "description": "rtmpdump binary for Linux 64 bit", + "time": "2016-04-12T19:17:32+00:00" + }, { "name": "rudloff/smarty-plugin-noscheme", "version": "0.1.1", @@ -612,16 +871,16 @@ }, { "name": "symfony/process", - "version": "v3.2.1", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "02ea84847aad71be7e32056408bb19f3a616cdd3" + "reference": "68bfa8c83f24c0ac04ea7193bcdcda4519f41892" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/02ea84847aad71be7e32056408bb19f3a616cdd3", - "reference": "02ea84847aad71be7e32056408bb19f3a616cdd3", + "url": "https://api.github.com/repos/symfony/process/zipball/68bfa8c83f24c0ac04ea7193bcdcda4519f41892", + "reference": "68bfa8c83f24c0ac04ea7193bcdcda4519f41892", "shasum": "" }, "require": { @@ -657,20 +916,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-11-24T10:40:28+00:00" + "time": "2017-03-04T12:23:14+00:00" }, { "name": "symfony/yaml", - "version": "v3.2.1", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "a7095af4b97a0955f85c8989106c249fa649011f" + "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/a7095af4b97a0955f85c8989106c249fa649011f", - "reference": "a7095af4b97a0955f85c8989106c249fa649011f", + "url": "https://api.github.com/repos/symfony/yaml/zipball/093e416ad096355149e265ea2e4cc1f9ee40ab1a", + "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a", "shasum": "" }, "require": { @@ -712,7 +971,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-12-10T10:07:06+00:00" + "time": "2017-03-07T16:47:02+00:00" } ], "packages-dev": [ @@ -775,7 +1034,7 @@ "version": "dev-release", "dist": { "type": "xz", - "url": "http://johnvansickle.com/ffmpeg/releases/ffmpeg-release-64bit-static.tar.xz", + "url": "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-64bit-static.tar.xz", "reference": null, "shasum": null }, @@ -786,16 +1045,16 @@ }, { "name": "heroku/heroku-buildpack-php", - "version": "v117", + "version": "v120", "source": { "type": "git", "url": "https://github.com/heroku/heroku-buildpack-php.git", - "reference": "960199a978308c75926fd9bb4775f7113bf1d777" + "reference": "e0499a7fdffd56f46534a037a6c48d65cef4e645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/heroku/heroku-buildpack-php/zipball/960199a978308c75926fd9bb4775f7113bf1d777", - "reference": "960199a978308c75926fd9bb4775f7113bf1d777", + "url": "https://api.github.com/repos/heroku/heroku-buildpack-php/zipball/e0499a7fdffd56f46534a037a6c48d65cef4e645", + "reference": "e0499a7fdffd56f46534a037a6c48d65cef4e645", "shasum": "" }, "bin": [ @@ -826,20 +1085,20 @@ "nginx", "php" ], - "time": "2016-12-09T19:37:38+00:00" + "time": "2017-02-20T15:05:49+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.5.5", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "399c1f9781e222f6eb6cc238796f5200d1b7f108" + "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/399c1f9781e222f6eb6cc238796f5200d1b7f108", - "reference": "399c1f9781e222f6eb6cc238796f5200d1b7f108", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", "shasum": "" }, "require": { @@ -868,7 +1127,7 @@ "object", "object graph" ], - "time": "2016-10-31T17:19:45+00:00" + "time": "2017-01-26T22:05:40+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -1018,27 +1277,27 @@ }, { "name": "phpspec/prophecy", - "version": "v1.6.2", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", - "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0|^2.0" + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { - "phpspec/phpspec": "^2.0", + "phpspec/phpspec": "^2.5|^3.2", "phpunit/phpunit": "^4.8 || ^5.6.5" }, "type": "library", @@ -1077,39 +1336,39 @@ "spy", "stub" ], - "time": "2016-11-21T14:58:47+00:00" + "time": "2017-03-02T20:05:34+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "4.0.4", + "version": "4.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c14196e64a78570034afd0b7a9f3757ba71c2a0a" + "reference": "09e2277d14ea467e5a984010f501343ef29ffc69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c14196e64a78570034afd0b7a9f3757ba71c2a0a", - "reference": "c14196e64a78570034afd0b7a9f3757ba71c2a0a", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/09e2277d14ea467e5a984010f501343ef29ffc69", + "reference": "09e2277d14ea467e5a984010f501343ef29ffc69", "shasum": "" }, "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "^1.4.2", - "sebastian/code-unit-reverse-lookup": "~1.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "~1.0|~2.0" + "sebastian/version": "^1.0 || ^2.0" }, "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "^5.4" + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" }, "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.4.0", - "ext-xmlwriter": "*" + "ext-xdebug": "^2.5.1" }, "type": "library", "extra": { @@ -1140,7 +1399,7 @@ "testing", "xunit" ], - "time": "2016-12-20T15:22:42+00:00" + "time": "2017-03-01T09:12:17+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1232,25 +1491,30 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.8", + "version": "1.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4|~5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -1272,20 +1536,20 @@ "keywords": [ "timer" ], - "time": "2016-05-12T18:03:57+00:00" + "time": "2017-02-26T11:10:40+00:00" }, { "name": "phpunit/php-token-stream", - "version": "1.4.9", + "version": "1.4.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", - "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", "shasum": "" }, "require": { @@ -1321,20 +1585,20 @@ "keywords": [ "tokenizer" ], - "time": "2016-11-15T14:06:22+00:00" + "time": "2017-02-27T10:12:30+00:00" }, { "name": "phpunit/phpunit", - "version": "5.7.5", + "version": "5.7.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "50fd2be8f3e23e91da825f36f08e5f9633076ffe" + "reference": "dafc78e2a7d12139b0e97078d1082326bd09363d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/50fd2be8f3e23e91da825f36f08e5f9633076ffe", - "reference": "50fd2be8f3e23e91da825f36f08e5f9633076ffe", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/dafc78e2a7d12139b0e97078d1082326bd09363d", + "reference": "dafc78e2a7d12139b0e97078d1082326bd09363d", "shasum": "" }, "require": { @@ -1346,19 +1610,19 @@ "myclabs/deep-copy": "~1.3", "php": "^5.6 || ^7.0", "phpspec/prophecy": "^1.6.2", - "phpunit/php-code-coverage": "^4.0.3", + "phpunit/php-code-coverage": "^4.0.4", "phpunit/php-file-iterator": "~1.4", "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "^1.0.6", "phpunit/phpunit-mock-objects": "^3.2", - "sebastian/comparator": "~1.2.2", + "sebastian/comparator": "^1.2.4", "sebastian/diff": "~1.2", "sebastian/environment": "^1.3.4 || ^2.0", "sebastian/exporter": "~2.0", - "sebastian/global-state": "^1.0 || ^2.0", + "sebastian/global-state": "^1.1", "sebastian/object-enumerator": "~2.0", "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0|~2.0", + "sebastian/version": "~1.0.3|~2.0", "symfony/yaml": "~2.1|~3.0" }, "conflict": { @@ -1403,7 +1667,7 @@ "testing", "xunit" ], - "time": "2016-12-28T07:18:51+00:00" + "time": "2017-03-15T13:02:34+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -1466,62 +1730,34 @@ }, { "name": "rg3/youtube-dl", - "version": "2017.01.10", + "version": "2017.03.07", "dist": { "type": "zip", - "url": "https://github.com/rg3/youtube-dl/archive/2017.01.10.zip", + "url": "https://github.com/rg3/youtube-dl/archive/2017.03.07.zip", "reference": null, "shasum": null }, "type": "library" }, - { - "name": "rudloff/rtmpdump-bin", - "version": "2.3", - "source": { - "type": "git", - "url": "https://github.com/Rudloff/rtmpdump-bin.git", - "reference": "133cdd80e3bab66593e88a5276158596383afd97" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Rudloff/rtmpdump-bin/zipball/133cdd80e3bab66593e88a5276158596383afd97", - "reference": "133cdd80e3bab66593e88a5276158596383afd97", - "shasum": "" - }, - "require-dev": { - "rtmpdump/rtmpdump": "2.3" - }, - "bin": [ - "rtmpdump" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0" - ], - "description": "rtmpdump binary for Linux 64 bit", - "time": "2016-04-12T19:17:32+00:00" - }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", "shasum": "" }, "require": { - "php": ">=5.6" + "php": "^5.6 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^5.7 || ^6.0" }, "type": "library", "extra": { @@ -1546,20 +1782,20 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2016-02-13T06:45:14+00:00" + "time": "2017-03-04T06:30:41+00:00" }, { "name": "sebastian/comparator", - "version": "1.2.2", + "version": "1.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f" + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a1ed12e8b2409076ab22e3897126211ff8b1f7f", - "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", "shasum": "" }, "require": { @@ -1610,7 +1846,7 @@ "compare", "equality" ], - "time": "2016-11-19T09:18:40+00:00" + "time": "2017-01-29T09:50:25+00:00" }, { "name": "sebastian/diff", @@ -1834,16 +2070,16 @@ }, { "name": "sebastian/object-enumerator", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35" + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", - "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", "shasum": "" }, "require": { @@ -1876,7 +2112,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2016-11-19T07:35:10+00:00" + "time": "2017-02-18T15:18:39+00:00" }, { "name": "sebastian/recursion-context", @@ -2155,22 +2391,25 @@ }, { "name": "symfony/var-dumper", - "version": "v3.2.1", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "f722532b0966e9b6fc631e682143c07b2cf583a0" + "reference": "4100f347aff890bc16b0b4b42843b599db257b2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f722532b0966e9b6fc631e682143c07b2cf583a0", - "reference": "f722532b0966e9b6fc631e682143c07b2cf583a0", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/4100f347aff890bc16b0b4b42843b599db257b2d", + "reference": "4100f347aff890bc16b0b4b42843b599db257b2d", "shasum": "" }, "require": { "php": ">=5.5.9", "symfony/polyfill-mbstring": "~1.0" }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, "require-dev": { "twig/twig": "~1.20|~2.0" }, @@ -2214,7 +2453,7 @@ "debug", "dump" ], - "time": "2016-12-11T14:34:22+00:00" + "time": "2017-02-20T13:45:48+00:00" }, { "name": "webmozart/assert", diff --git a/config.example.yml b/config.example.yml index 7fb80ef..6bb93e4 100644 --- a/config.example.yml +++ b/config.example.yml @@ -3,7 +3,6 @@ python: /usr/bin/python params: - --no-playlist - --no-warnings - - -f best[protocol^=http] - --playlist-end - 1 curl_params: @@ -12,3 +11,4 @@ avconv: vendor/bin/ffmpeg rtmpdump: vendor/bin/rtmpdump curl: /usr/bin/curl uglyUrls: false +stream: false diff --git a/controllers/FrontController.php b/controllers/FrontController.php index 04734da..84ef036 100644 --- a/controllers/FrontController.php +++ b/controllers/FrontController.php @@ -54,6 +54,13 @@ class FrontController */ private $view; + /** + * Default youtube-dl format. + * + * @var string + */ + private $defaultFormat = 'best[protocol^=http]'; + /** * FrontController constructor. * @@ -68,6 +75,9 @@ class FrontController $session_factory = new \Aura\Session\SessionFactory(); $session = $session_factory->newInstance($_COOKIE); $this->sessionSegment = $session->getSegment('Alltube\Controller\FrontController'); + if ($this->config->stream) { + $this->defaultFormat = 'best'; + } } /** @@ -76,10 +86,11 @@ class FrontController * @param Request $request PSR-7 request * @param Response $response PSR-7 response * - * @return void + * @return Response HTTP response */ public function index(Request $request, Response $response) { + $uri = $request->getUri(); $this->view->render( $response, 'index.tpl', @@ -88,8 +99,12 @@ class FrontController 'uglyUrls' => $this->config->uglyUrls, 'class' => 'index', 'description' => 'Easily download videos from Youtube, Dailymotion, Vimeo and other websites.', + 'domain' => $uri->getScheme().'://'.$uri->getAuthority(), + 'canonical' => $this->getCanonicalUrl($request), ] ); + + return $response; } /** @@ -98,7 +113,7 @@ class FrontController * @param Request $request PSR-7 request * @param Response $response PSR-7 response * - * @return void + * @return Response HTTP response */ public function extractors(Request $request, Response $response) { @@ -111,8 +126,11 @@ class FrontController 'title' => 'Supported websites', 'description' => 'List of all supported websites from which Alltube Download '. 'can extract video or audio files', + 'canonical' => $this->getCanonicalUrl($request), ] ); + + return $response; } /** @@ -132,8 +150,89 @@ class FrontController 'class' => 'password', 'title' => 'Password prompt', 'description' => 'You need a password in order to download this video with Alltube Download', + 'canonical' => $this->getCanonicalUrl($request), ] ); + + return $response; + } + + /** + * Return the converted MP3 file. + * + * @param Request $request PSR-7 request + * @param Response $response PSR-7 response + * @param array $params GET query parameters + * @param string $password Video password + * + * @return Response HTTP response + */ + private function getAudioResponse(Request $request, Response $response, array $params, $password = null) + { + try { + if ($this->config->stream) { + return $this->getStream($params['url'], 'mp3', $response, $request, $password); + } else { + $url = $this->download->getURL($params['url'], 'mp3[protocol^=http]', $password); + + return $response->withRedirect($url); + } + } catch (PasswordException $e) { + return $this->password($request, $response); + } catch (\Exception $e) { + $response = $response->withHeader( + 'Content-Disposition', + 'attachment; filename="'. + $this->download->getAudioFilename($params['url'], 'bestaudio/best', $password).'"' + ); + $response = $response->withHeader('Content-Type', 'audio/mpeg'); + + if ($request->isGet() || $request->isPost()) { + $process = $this->download->getAudioStream($params['url'], 'bestaudio/best', $password); + $response = $response->withBody(new Stream($process)); + } + + return $response; + } + } + + /** + * Return the video description page. + * + * @param Request $request PSR-7 request + * @param Response $response PSR-7 response + * @param array $params GET query parameters + * @param string $password Video password + * + * @return Response HTTP response + */ + private function getVideoResponse(Request $request, Response $response, array $params, $password = null) + { + try { + $video = $this->download->getJSON($params['url'], $this->defaultFormat, $password); + } catch (PasswordException $e) { + return $this->password($request, $response); + } + if ($this->config->stream) { + $protocol = ''; + } else { + $protocol = '[protocol^=http]'; + } + $this->view->render( + $response, + 'video.tpl', + [ + 'video' => $video, + 'class' => 'video', + 'title' => $video->title, + 'description' => 'Download "'.$video->title.'" from '.$video->extractor_key, + 'protocol' => $protocol, + 'config' => $this->config, + 'canonical' => $this->getCanonicalUrl($request), + ] + ); + + return $response; } /** @@ -153,43 +252,9 @@ class FrontController $this->sessionSegment->setFlash($params['url'], $password); } if (isset($params['audio'])) { - try { - $url = $this->download->getURL($params['url'], 'mp3[protocol^=http]', $password); - - return $response->withRedirect($url); - } catch (PasswordException $e) { - return $this->password($request, $response); - } catch (\Exception $e) { - $response = $response->withHeader( - 'Content-Disposition', - 'attachment; filename="'. - $this->download->getAudioFilename($params['url'], 'bestaudio/best', $password).'"' - ); - $response = $response->withHeader('Content-Type', 'audio/mpeg'); - - if ($request->isGet() || $request->isPost()) { - $process = $this->download->getAudioStream($params['url'], 'bestaudio/best', $password); - $response = $response->withBody(new Stream($process)); - } - - return $response; - } + return $this->getAudioResponse($request, $response, $params, $password); } else { - try { - $video = $this->download->getJSON($params['url'], null, $password); - } catch (PasswordException $e) { - return $this->password($request, $response); - } - $this->view->render( - $response, - 'video.tpl', - [ - 'video' => $video, - 'class' => 'video', - 'title' => $video->title, - 'description' => 'Download "'.$video->title.'" from '.$video->extractor_key, - ] - ); + return $this->getVideoResponse($request, $response, $params, $password); } } else { return $response->withRedirect($this->container->get('router')->pathFor('index')); @@ -211,15 +276,50 @@ class FrontController $response, 'error.tpl', [ - 'errors' => $exception->getMessage(), - 'class' => 'video', - 'title' => 'Error', + 'errors' => $exception->getMessage(), + 'class' => 'video', + 'title' => 'Error', + 'canonical' => $this->getCanonicalUrl($request), ] ); return $response->withStatus(500); } + /** + * Get a video/audio stream piped through the server. + * + * @param string $url URL of the video + * @param string $format Requested format + * @param Response $response PSR-7 response + * @param Request $request PSR-7 request + * @param string $password Video password + * + * @return Response HTTP response + */ + private function getStream($url, $format, $response, $request, $password = null) + { + $video = $this->download->getJSON($url, $format, $password); + if ($video->protocol == 'm3u8') { + $stream = $this->download->getM3uStream($video); + $response = $response->withHeader('Content-Type', 'video/'.$video->ext); + if ($request->isGet()) { + $response = $response->withBody(new Stream($stream)); + } + } else { + $client = new \GuzzleHttp\Client(); + $stream = $client->request('GET', $video->url, ['stream' => true]); + $response = $response->withHeader('Content-Type', $stream->getHeader('Content-Type')); + $response = $response->withHeader('Content-Length', $stream->getHeader('Content-Length')); + if ($request->isGet()) { + $response = $response->withBody($stream->getBody()); + } + } + $response = $response->withHeader('Content-Disposition', 'attachment; filename="'.$video->_filename.'"'); + + return $response; + } + /** * Redirect to video file. * @@ -231,15 +331,30 @@ class FrontController public function redirect(Request $request, Response $response) { $params = $request->getQueryParams(); + if (isset($params['format'])) { + $format = $params['format']; + } else { + $format = $this->defaultFormat; + } if (isset($params['url'])) { try { - $url = $this->download->getURL( - $params['url'], - $request->getParam('format'), - $this->sessionSegment->getFlash($params['url']) - ); + if ($this->config->stream) { + return $this->getStream( + $params['url'], + $format, + $response, + $request, + $this->sessionSegment->getFlash($params['url']) + ); + } else { + $url = $this->download->getURL( + $params['url'], + $format, + $this->sessionSegment->getFlash($params['url']) + ); - return $response->withRedirect($url); + return $response->withRedirect($url); + } } catch (PasswordException $e) { return $response->withRedirect( $this->container->get('router')->pathFor('video').'?url='.urlencode($params['url']) @@ -247,32 +362,35 @@ class FrontController } catch (\Exception $e) { $response->getBody()->write($e->getMessage()); - return $response->withHeader('Content-Type', 'text/plain'); + return $response->withHeader('Content-Type', 'text/plain')->withStatus(500); } + } else { + return $response->withRedirect($this->container->get('router')->pathFor('index')); } } /** - * Output JSON info about the video. + * Generate the canonical URL of the current page. * - * @param Request $request PSR-7 request - * @param Response $response PSR-7 response + * @param Request $request PSR-7 Request * - * @return Response HTTP response + * @return string URL */ - public function json(Request $request, Response $response) + private function getCanonicalUrl(Request $request) { - $params = $request->getQueryParams(); - if (isset($params['url'])) { - try { - $video = $this->download->getJSON($params['url']); + $uri = $request->getUri(); + $return = 'https://alltubedownload.net/'; - return $response->withJson($video); - } catch (\Exception $e) { - return $response->withJson( - ['success' => false, 'error' => $e->getMessage()] - ); - } + $path = $uri->getPath(); + if ($path != '/') { + $return .= $path; } + + $query = $uri->getQuery(); + if (!empty($query)) { + $return .= '?'.$query; + } + + return $return; } } diff --git a/index.php b/index.php index fad0bb0..b3b77de 100644 --- a/index.php +++ b/index.php @@ -48,8 +48,4 @@ $app->get( '/redirect', [$controller, 'redirect'] )->setName('redirect'); -$app->get( - '/json', - [$controller, 'json'] -); $app->run(); diff --git a/package.json b/package.json index 50a9ff1..69ce6d1 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,17 @@ { "name": "alltube", "description": "HTML GUI for youtube-dl", - "version": "0.7.2-beta", + "version": "0.7.2-beta2", "author": "Pierre Rudloff", "bugs": "https://github.com/Rudloff/alltube/issues", "dependencies": { "bower": "~1.8.0", "grunt": "~1.0.1", - "grunt-contrib-cssmin": "~1.0.0", - "grunt-contrib-uglify": "~2.0.0" + "grunt-contrib-cssmin": "~2.0.0", + "grunt-contrib-uglify": "~2.2.0" }, "devDependencies": { - "grunt-contrib-compress": "~1.3.0", + "grunt-contrib-compress": "~1.4.1", "grunt-contrib-watch": "~1.0.0", "grunt-fixpack": "~0.1.0", "grunt-githash": "~0.1.3", @@ -27,7 +27,7 @@ "homepage": "https://www.alltubedownload.net/", "keywords": [ "alltube", - "dowload", + "download", "video", "youtube" ], diff --git a/phpunit.xml b/phpunit.xml index fe96cd9..f37d2a5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,8 @@ - + classes/ + controllers/ diff --git a/templates/inc/head.tpl b/templates/inc/head.tpl index bf20a4c..98a0784 100644 --- a/templates/inc/head.tpl +++ b/templates/inc/head.tpl @@ -10,7 +10,7 @@ {/if} AllTube Download{if isset($title)} - {$title|escape}{/if} - + diff --git a/templates/index.tpl b/templates/index.tpl index 66eb1ab..9f3583c 100644 --- a/templates/index.tpl +++ b/templates/index.tpl @@ -12,7 +12,7 @@ - {if uglyUrls} + {if $uglyUrls} {/if}
@@ -28,7 +28,7 @@ See all supported websites

Drag this to your bookmarks bar:

- Bookmarklet + Bookmarklet
diff --git a/templates/video.tpl b/templates/video.tpl index 678ffbd..5b82363 100644 --- a/templates/video.tpl +++ b/templates/video.tpl @@ -29,18 +29,18 @@ {/if}
{else} - + Download
{/if} diff --git a/tests/FrontControllerTest.php b/tests/FrontControllerTest.php new file mode 100644 index 0000000..7152a24 --- /dev/null +++ b/tests/FrontControllerTest.php @@ -0,0 +1,370 @@ +container = new Container(); + $this->request = Request::createFromEnvironment(Environment::mock()); + $this->response = new Response(); + $this->container['view'] = function ($c) { + $view = new \Slim\Views\Smarty(__DIR__.'/../templates/'); + + $smartyPlugins = new \Slim\Views\SmartyPlugins($c['router'], $this->request->getUri()); + $view->registerPlugin('function', 'path_for', [$smartyPlugins, 'pathFor']); + $view->registerPlugin('function', 'base_url', [$smartyPlugins, 'baseUrl']); + + $view->registerPlugin('modifier', 'noscheme', 'Smarty_Modifier_noscheme'); + + return $view; + }; + $this->controller = new FrontController($this->container); + $this->container['router']->map(['GET'], '/', [$this->controller, 'index']) + ->setName('index'); + $this->container['router']->map(['GET'], '/video', [$this->controller, 'video']) + ->setName('video'); + $this->container['router']->map(['GET'], '/extractors', [$this->controller, 'extractors']) + ->setName('extractors'); + $this->container['router']->map(['GET'], '/redirect', [$this->controller, 'redirect']) + ->setName('redirect'); + } + + /** + * Destroy properties after test. + */ + protected function tearDown() + { + Config::destroyInstance(); + } + + /** + * Test the constructor with streams enabled. + * + * @return void + */ + public function testConstructorWithStream() + { + $config = Config::getInstance(); + $config->stream = true; + $controller = new FrontController($this->container); + $this->assertInstanceOf(FrontController::class, $controller); + } + + /** + * Test the index() function. + * + * @return void + */ + public function testIndex() + { + $result = $this->controller->index($this->request, $this->response); + $this->assertTrue($result->isOk()); + } + + /** + * Test the index() function with a custom URI. + * + * @return void + */ + public function testIndexWithCustomUri() + { + $result = $this->controller->index( + Request::createFromEnvironment( + Environment::mock(['REQUEST_URI'=>'/foo', 'QUERY_STRING'=>'foo=bar']) + ), + $this->response + ); + $this->assertTrue($result->isOk()); + } + + /** + * Test the extractors() function. + * + * @return void + */ + public function testExtractors() + { + $result = $this->controller->extractors($this->request, $this->response); + $this->assertTrue($result->isOk()); + } + + /** + * Test the password() function. + * + * @return void + */ + public function testPassword() + { + $result = $this->controller->password($this->request, $this->response); + $this->assertTrue($result->isOk()); + } + + /** + * Test the video() function without the url parameter. + * + * @return void + */ + public function testVideoWithoutUrl() + { + $result = $this->controller->video($this->request, $this->response); + $this->assertTrue($result->isRedirect()); + } + + /** + * Test the video() function. + * + * @return void + */ + public function testVideo() + { + $result = $this->controller->video( + $this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']), + $this->response + ); + $this->assertTrue($result->isOk()); + } + + /** + * Test the video() function with audio conversion. + * + * @return void + */ + public function testVideoWithAudio() + { + $result = $this->controller->video( + $this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'audio'=>true]), + $this->response + ); + $this->assertTrue($result->isOk()); + } + + /** + * Test the video() function with audio enabled and an URL that doesn't need to be converted. + * + * @return void + */ + public function testVideoWithUnconvertedAudio() + { + $result = $this->controller->video( + $this->request->withQueryParams( + ['url' => 'https://soundcloud.com/verwandlungskuenstler/metamorphosis-by-franz-kafka-1', + 'audio'=> true, ] + ), + $this->response + ); + $this->assertTrue($result->isRedirect()); + } + + /** + * Test the video() function with a password. + * + * @return void + */ + public function testVideoWithPassword() + { + $result = $this->controller->video( + $this->request->withQueryParams(['url'=>'http://vimeo.com/68375962']) + ->withParsedBody(['password'=>'youtube-dl']), + $this->response + ); + $this->assertTrue($result->isOk()); + } + + /** + * Test the video() function with a missing password. + * + * @return void + */ + public function testVideoWithMissingPassword() + { + $result = $this->controller->video( + $this->request->withQueryParams(['url'=>'http://vimeo.com/68375962']), + $this->response + ); + $this->assertTrue($result->isOk()); + $result = $this->controller->video( + $this->request->withQueryParams(['url'=>'http://vimeo.com/68375962', 'audio'=>true]), + $this->response + ); + $this->assertTrue($result->isOk()); + } + + /** + * Test the video() function with streams enabled. + * + * @return void + */ + public function testVideoWithStream() + { + $config = Config::getInstance(); + $config->stream = true; + $result = $this->controller->video( + $this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']), + $this->response + ); + $this->assertTrue($result->isOk()); + $result = $this->controller->video( + $this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'audio'=>true]), + $this->response + ); + $this->assertTrue($result->isOk()); + } + + /** + * Test the error() function. + * + * @return void + */ + public function testError() + { + $result = $this->controller->error($this->request, $this->response, new \Exception('foo')); + $this->assertTrue($result->isServerError()); + } + + /** + * Test the redirect() function without the URL parameter. + * + * @return void + */ + public function testRedirectWithoutUrl() + { + $result = $this->controller->redirect($this->request, $this->response); + $this->assertTrue($result->isRedirect()); + } + + /** + * Test the redirect() function. + * + * @return void + */ + public function testRedirect() + { + $result = $this->controller->redirect( + $this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']), + $this->response + ); + $this->assertTrue($result->isRedirect()); + } + + /** + * Test the redirect() function with a specific format. + * + * @return void + */ + public function testRedirectWithFormat() + { + $result = $this->controller->redirect( + $this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'format'=>'worst']), + $this->response + ); + $this->assertTrue($result->isRedirect()); + } + + /** + * Test the redirect() function with streams enabled. + * + * @return void + */ + public function testRedirectWithStream() + { + $config = Config::getInstance(); + $config->stream = true; + $result = $this->controller->redirect( + $this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']), + $this->response + ); + $this->assertTrue($result->isOk()); + } + + /** + * Test the redirect() function with an M3U stream. + * + * @return void + */ + public function testRedirectWithM3uStream() + { + $config = Config::getInstance(); + $config->stream = true; + //We need to create a new controller instance in order to apply the custom config + $controller = new FrontController($this->container); + $result = $controller->redirect( + $this->request->withQueryParams(['url'=>'https://twitter.com/verge/status/813055465324056576/video/1']), + $this->response + ); + $this->assertTrue($result->isOk()); + } + + /** + * Test the redirect() function with a missing password. + * + * @return void + */ + public function testRedirectWithMissingPassword() + { + $result = $this->controller->redirect( + $this->request->withQueryParams(['url'=>'http://vimeo.com/68375962']), + $this->response + ); + $this->assertTrue($result->isRedirect()); + } + + /** + * Test the redirect() function with an error. + * + * @return void + */ + public function testRedirectWithError() + { + $result = $this->controller->redirect( + $this->request->withQueryParams(['url'=>'http://example.com/foo']), + $this->response + ); + $this->assertTrue($result->isServerError()); + } +} diff --git a/tests/UglyRouterTest.php b/tests/UglyRouterTest.php index 3d211c5..4d21b3e 100644 --- a/tests/UglyRouterTest.php +++ b/tests/UglyRouterTest.php @@ -7,10 +7,7 @@ namespace Alltube\Test; use Alltube\UglyRouter; use Slim\Http\Environment; -use Slim\Http\Headers; use Slim\Http\Request; -use Slim\Http\Stream; -use Slim\Http\Uri; /** * Unit tests for the UglyRouter class. @@ -43,13 +40,13 @@ class UglyRouterTest extends \PHPUnit_Framework_TestCase $this->assertEquals( [1, 'route0', []], $this->router->dispatch( - new Request( - 'GET', - Uri::createFromString('http://example.com/?page=foo'), - Headers::createFromEnvironment(new Environment()), - [], - [], - new Stream(fopen('php://temp', 'r')) + Request::createFromEnvironment( + Environment::mock( + [ + 'REQUEST_METHOD' => 'GET', + 'QUERY_STRING' => 'page=foo', + ] + ) ) ) ); diff --git a/tests/VideoDownloadTest.php b/tests/VideoDownloadTest.php index 8af3275..39119db 100644 --- a/tests/VideoDownloadTest.php +++ b/tests/VideoDownloadTest.php @@ -146,7 +146,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase { return [ [ - 'https://www.youtube.com/watch?v=M7IpKCZ47pU', null, + 'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'best[protocol^=http]', "It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU", 'mp4', 'googlevideo.com', @@ -159,7 +159,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase 'googlevideo.com', ], [ - 'https://vimeo.com/24195442', null, + 'https://vimeo.com/24195442', 'best[protocol^=http]', 'Carving the Mountains-24195442', 'mp4', 'vimeocdn.com', @@ -179,6 +179,23 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase ]; } + /** + * Provides M3U8 URLs for tests. + * + * @return array[] + */ + public function m3uUrlProvider() + { + return [ + [ + 'https://twitter.com/verge/status/813055465324056576/video/1', 'best', + 'The Verge - This tiny origami robot can self-fold and complete tasks-813055465324056576', + 'mp4', + 'video.twimg.com', + ], + ]; + } + /** * Provides incorrect URLs for tests. * @@ -199,6 +216,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase * * @return void * @dataProvider URLProvider + * @dataProvider m3uUrlProvider */ public function testGetJSON($url, $format) { @@ -207,6 +225,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase $this->assertObjectHasAttribute('url', $info); $this->assertObjectHasAttribute('ext', $info); $this->assertObjectHasAttribute('title', $info); + $this->assertObjectHasAttribute('extractor_key', $info); $this->assertObjectHasAttribute('formats', $info); $this->assertObjectHasAttribute('_filename', $info); } @@ -222,7 +241,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase */ public function testGetJSONError($url) { - $videoURL = $this->download->getJSON($url); + $this->download->getJSON($url); } /** @@ -235,6 +254,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase * * @return void * @dataProvider urlProvider + * @dataProvider m3uUrlProvider */ public function testGetFilename($url, $format, $filename, $extension) { @@ -259,14 +279,13 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase /** * 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 + * @param string $url URL + * @param string $format Format + * @param string $filename Filename * * @return void * @dataProvider urlProvider + * @dataProvider m3uUrlProvider */ public function testGetAudioFilename($url, $format, $filename) { @@ -302,7 +321,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase */ public function testGetAudioStreamAvconvError($url, $format) { - $config = \Alltube\Config::getInstance(); + $config = Config::getInstance(); $config->avconv = 'foobar'; $this->download->getAudioStream($url, $format); } @@ -319,7 +338,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase */ public function testGetAudioStreamCurlError($url, $format) { - $config = \Alltube\Config::getInstance(); + $config = Config::getInstance(); $config->curl = 'foobar'; $config->rtmpdump = 'foobar'; $this->download->getAudioStream($url, $format); @@ -328,11 +347,50 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase /** * Test getAudioStream function with a M3U8 file. * + * @param string $url URL + * @param string $format Format + * * @return void * @expectedException Exception + * @dataProvider m3uUrlProvider */ - public function testGetAudioStreamM3uError() + public function testGetAudioStreamM3uError($url, $format) { - $this->download->getAudioStream('https://twitter.com/verge/status/813055465324056576/video/1', 'best'); + $this->download->getAudioStream($url, $format); + } + + /** + * Test getM3uStream function. + * + * @param string $url URL + * @param string $format Format + * + * @return void + * @dataProvider m3uUrlProvider + */ + public function testGetM3uStream($url, $format) + { + $video = $this->download->getJSON($url, $format); + $stream = $this->download->getM3uStream($video); + $this->assertInternalType('resource', $stream); + $this->assertFalse(feof($stream)); + } + + /** + * Test getM3uStream function without avconv. + * + * @param string $url URL + * @param string $format Format + * + * @return void + * @expectedException Exception + * @dataProvider m3uUrlProvider + */ + public function testGetM3uStreamAvconvError($url, $format) + { + $config = \Alltube\Config::getInstance(); + $config->avconv = 'foobar'; + $video = $this->download->getJSON($url, $format); + $this->download->getM3uStream($video); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..074ec50 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,11 @@ +