Merge branch 'release-0.9.0'

This commit is contained in:
Pierre Rudloff 2017-04-28 16:27:35 +02:00
commit f53fc5ebc1
19 changed files with 704 additions and 280 deletions

15
FAQ.md
View file

@ -15,10 +15,10 @@ 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
* `remux`: enable remux mode (experimental)
See [`config.example.yml`](config.example.yml) for default values.
@ -31,10 +31,10 @@ convert: true
avconv: path/to/avconv
```
You will also need to install `avconv` and `curl` on your server:
You will also need to install `avconv` on your server:
```bash
sudo apt-get install libav-tools curl
sudo apt-get install libav-tools
```
## How do I deploy Alltube on Heroku?
@ -129,3 +129,12 @@ And you probably need to run this in another terminal after `heroku local` has f
```bash
chmod 0667 /tmp/heroku.fcgi.5000.sock
```
## How can I download 1080p videos from Youtube?
Youtube distributes HD content in two separate video and audio files.
So Alltube will offer you video-only and audio-only formats in the format list.
You then need to merge them together with a tool like ffmpeg.
You can also enable the experimental remux mode that will merge the best video and the best audio format on the fly.

View file

@ -122,13 +122,13 @@ server {
## 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) and [rtmpdump](http://rtmpdump.mplayerhq.hu/) in order to enable conversions.
If you don't want to enable conversions, you can disable it in `config.yml`.
On Debian-based systems:
```bash
sudo apt-get install libav-tools rtmpdump curl
sudo apt-get install libav-tools rtmpdump
```
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

@ -38,7 +38,7 @@ class Config
*
* @var array
*/
public $params = ['--no-playlist', '--no-warnings', '--playlist-end', 1];
public $params = ['--no-warnings', '--ignore-errors', '--flat-playlist'];
/**
* Enable audio conversion.
@ -61,20 +61,6 @@ class Config
*/
public $rtmpdump = 'vendor/bin/rtmpdump';
/**
* curl binary path.
*
* @var string
*/
public $curl = '/usr/bin/curl';
/**
* curl parameters.
*
* @var array
*/
public $curl_params = [];
/**
* Disable URL rewriting.
*
@ -89,6 +75,13 @@ class Config
*/
public $stream = false;
/**
* Allow to remux video + audio?
*
* @var bool
*/
public $remux = false;
/**
* YAML config file path.
*
@ -104,9 +97,7 @@ class Config
* * python: Python binary path
* * avconv: avconv or ffmpeg binary path
* * rtmpdump: rtmpdump binary path
* * curl: curl binary path
* * params: Array of youtube-dl parameters
* * curl_params: Array of curl parameters
* * convert: Enable conversion?
*
* @param array $options Options
@ -141,7 +132,7 @@ class Config
if (is_null(self::$instance) || self::$instance->file != $yamlfile) {
if (is_file($yamlfile)) {
$options = Yaml::parse(file_get_contents($yamlPath));
} elseif ($yamlfile == 'config.yml') {
} elseif ($yamlfile == 'config.yml' || empty($yamlfile)) {
/*
Allow for the default file to be missing in order to
not surprise users that did not create a config file

View file

@ -98,7 +98,7 @@ class VideoDownload
throw new \Exception($errorOutput);
}
} else {
return $process->getOutput();
return trim($process->getOutput());
}
}
@ -113,21 +113,25 @@ class VideoDownload
* */
public function getJSON($url, $format = null, $password = null)
{
return json_decode($this->getProp($url, $format, 'dump-json', $password));
return json_decode($this->getProp($url, $format, 'dump-single-json', $password));
}
/**
* Get URL of video from URL of page.
*
* It generally returns only one URL.
* But it can return two URLs when multiple formats are specified
* (eg. bestvideo+bestaudio).
*
* @param string $url URL of page
* @param string $format Format to use for the video
* @param string $password Video password
*
* @return string URL of video
* @return string[] URLs of video
* */
public function getURL($url, $format = null, $password = null)
{
return $this->getProp($url, $format, 'get-url', $password);
return explode(PHP_EOL, $this->getProp($url, $format, 'get-url', $password));
}
/**
@ -144,6 +148,28 @@ class VideoDownload
return trim($this->getProp($url, $format, 'get-filename', $password));
}
/**
* Get filename of video with the specified extension.
*
* @param string $extension New file extension
* @param string $url URL of page
* @param string $format Format to use for the video
* @param string $password Video password
*
* @return string Filename of extracted video with specified extension
*/
public function getFileNameWithExtension($extension, $url, $format = null, $password = null)
{
return html_entity_decode(
pathinfo(
$this->getFilename($url, $format, $password),
PATHINFO_FILENAME
).'.'.$extension,
ENT_COMPAT,
'ISO-8859-1'
);
}
/**
* Get filename of audio from URL of page.
*
@ -155,14 +181,7 @@ class VideoDownload
* */
public function getAudioFilename($url, $format = null, $password = null)
{
return html_entity_decode(
pathinfo(
$this->getFilename($url, $format, $password),
PATHINFO_FILENAME
).'.mp3',
ENT_COMPAT,
'ISO-8859-1'
);
return $this->getFileNameWithExtension('mp3', $url, $format, $password);
}
/**
@ -222,31 +241,30 @@ class VideoDownload
}
/**
* Get a process that runs curl in order to download a video.
* Get a process that runs avconv in order to convert a video to MP3.
*
* @param object $video Video object returned by youtube-dl
* @param string $url URL of the video file
*
* @return \Symfony\Component\Process\Process Process
*/
private function getCurlProcess($video)
private function getAvconvMp3Process($url)
{
if (!shell_exec('which '.$this->config->curl)) {
throw(new \Exception('Can\'t find curl'));
if (!shell_exec('which '.$this->config->avconv)) {
throw(new \Exception('Can\'t find avconv or ffmpeg'));
}
$builder = ProcessBuilder::create(
array_merge(
[
$this->config->curl,
'--silent',
'--location',
'--user-agent', $video->http_headers->{'User-Agent'},
$video->url,
],
$this->config->curl_params
)
);
return $builder->getProcess();
return ProcessBuilder::create(
[
$this->config->avconv,
'-v', 'quiet',
//Vimeo needs a correct user-agent
'-user-agent', $this->getProp(null, null, 'dump-user-agent'),
'-i', $url,
'-f', 'mp3',
'-vn',
'pipe:1',
]
);
}
/**
@ -260,40 +278,22 @@ class VideoDownload
*/
public function getAudioStream($url, $format, $password = null)
{
if (!shell_exec('which '.$this->config->avconv)) {
throw(new \Exception('Can\'t find avconv or ffmpeg'));
}
$video = $this->getJSON($url, $format, $password);
if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) {
throw(new \Exception('Conversion of M3U8 files is not supported.'));
}
//Vimeo needs a correct user-agent
ini_set(
'user_agent',
$video->http_headers->{'User-Agent'}
);
$avconvProc = ProcessBuilder::create(
[
$this->config->avconv,
'-v', 'quiet',
'-i', '-',
'-f', 'mp3',
'-vn',
'pipe:1',
]
);
if (parse_url($video->url, PHP_URL_SCHEME) == 'rtmp') {
$process = $this->getRtmpProcess($video);
} else {
$process = $this->getCurlProcess($video);
}
$chain = new Chain($process);
$chain->add('|', $avconvProc);
$chain = new Chain($process);
$chain->add('|', $this->getAvconvMp3Process('-'));
return popen($chain->getProcess()->getCommandLine(), 'r');
return popen($chain->getProcess()->getCommandLine(), 'r');
} else {
$avconvProc = $this->getAvconvMp3Process($video->url);
return popen($avconvProc->getProcess()->getCommandLine(), 'r');
}
}
/**
@ -324,4 +324,42 @@ class VideoDownload
return popen($procBuilder->getProcess()->getCommandLine(), 'r');
}
/**
* Get an avconv stream to remux audio and video.
*
* @param array $urls URLs of the video ($urls[0]) and audio ($urls[1]) files
*
* @return resource popen stream
*/
public function getRemuxStream(array $urls)
{
$procBuilder = ProcessBuilder::create(
[
$this->config->avconv,
'-v', 'quiet',
'-i', $urls[0],
'-i', $urls[1],
'-c', 'copy',
'-map', '0:v:0 ',
'-map', '1:a:0',
'-f', 'matroska',
'pipe:1',
]
);
return popen($procBuilder->getProcess()->getCommandLine(), 'r');
}
/**
* Get video stream from an RTMP video.
*
* @param \stdClass $video Video object returned by getJSON
*
* @return resource popen stream
*/
public function getRtmpStream(\stdClass $video)
{
return popen($this->getRtmpProcess($video)->getCommandLine(), 'r');
}
}

42
classes/ViewFactory.php Normal file
View file

@ -0,0 +1,42 @@
<?php
/**
* ViewFactory class.
*/
namespace Alltube;
use Psr\Container\ContainerInterface;
use Slim\Http\Request;
use Slim\Views\Smarty;
use Slim\Views\SmartyPlugins;
/**
* Create Smarty view object.
*/
class ViewFactory
{
/**
* Create Smarty view object.
*
* @param Container $container Slim dependency container
* @param Request $request PSR-7 request
*
* @return Smarty
*/
public static function create(ContainerInterface $container, Request $request = null)
{
if (!isset($request)) {
$request = $container['request'];
}
$view = new Smarty(__DIR__.'/../templates/');
$smartyPlugins = new SmartyPlugins($container['router'], $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;
}
}

View file

@ -6,7 +6,7 @@
"type": "project",
"require": {
"smarty/smarty": "~3.1.29",
"slim/slim": "~3.7.0",
"slim/slim": "~3.8.1",
"mathmarques/smarty-view": "~1.1.0",
"symfony/yaml": "~3.2.0",
"symfony/process": "~3.2.0",
@ -21,7 +21,7 @@
"squizlabs/php_codesniffer": "~2.8.0",
"phpunit/phpunit": "~5.7.2",
"ffmpeg/ffmpeg": "dev-release",
"rg3/youtube-dl": "~2017.04.15",
"rg3/youtube-dl": "~2017.04.28",
"rudloff/rtmpdump-bin": "~2.3",
"heroku/heroku-buildpack-php": "*"
},
@ -37,10 +37,10 @@
"type": "package",
"package": {
"name": "rg3/youtube-dl",
"version": "2017.04.15",
"version": "2017.04.28",
"dist": {
"type": "zip",
"url": "https://github.com/rg3/youtube-dl/archive/2017.04.15.zip"
"url": "https://github.com/rg3/youtube-dl/archive/2017.04.28.zip"
}
}
},

19
composer.lock generated
View file

@ -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": "863f0c9fafcc85d185aa4441b0ad7e71",
"content-hash": "6fda75dec4d72b9620ca68697278dc7e",
"packages": [
{
"name": "aura/session",
@ -752,23 +752,24 @@
},
{
"name": "slim/slim",
"version": "3.7.0",
"version": "3.8.1",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim.git",
"reference": "4254e40d81559e35cdf856bcbaca5f3af468b7ef"
"reference": "5385302707530b2bccee1769613ad769859b826d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/4254e40d81559e35cdf856bcbaca5f3af468b7ef",
"reference": "4254e40d81559e35cdf856bcbaca5f3af468b7ef",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/5385302707530b2bccee1769613ad769859b826d",
"reference": "5385302707530b2bccee1769613ad769859b826d",
"shasum": ""
},
"require": {
"container-interop/container-interop": "^1.1",
"container-interop/container-interop": "^1.2",
"nikic/fast-route": "^1.0",
"php": ">=5.5.0",
"pimple/pimple": "^3.0",
"psr/container": "^1.0",
"psr/http-message": "^1.0"
},
"provide": {
@ -818,7 +819,7 @@
"micro",
"router"
],
"time": "2016-12-20T20:30:47+00:00"
"time": "2017-03-19T17:55:20+00:00"
},
{
"name": "smarty/smarty",
@ -1734,10 +1735,10 @@
},
{
"name": "rg3/youtube-dl",
"version": "2017.04.15",
"version": "2017.04.28",
"dist": {
"type": "zip",
"url": "https://github.com/rg3/youtube-dl/archive/2017.04.15.zip",
"url": "https://github.com/rg3/youtube-dl/archive/2017.04.28.zip",
"reference": null,
"shasum": null
},

View file

@ -5,10 +5,8 @@ params:
- --no-warnings
- --playlist-end
- 1
curl_params:
convert: false
avconv: vendor/bin/ffmpeg
rtmpdump: vendor/bin/rtmpdump
curl: /usr/bin/curl
uglyUrls: false
stream: false

View file

@ -8,7 +8,7 @@ namespace Alltube\Controller;
use Alltube\Config;
use Alltube\PasswordException;
use Alltube\VideoDownload;
use Interop\Container\ContainerInterface;
use Psr\Container\ContainerInterface;
use Slim\Container;
use Slim\Http\Request;
use Slim\Http\Response;
@ -65,15 +65,21 @@ class FrontController
* FrontController constructor.
*
* @param Container $container Slim dependency container
* @param Config $config Config instance
* @param array $cookies Cookie array
*/
public function __construct(ContainerInterface $container)
public function __construct(ContainerInterface $container, Config $config = null, array $cookies = [])
{
$this->config = Config::getInstance();
if (isset($config)) {
$this->config = $config;
} else {
$this->config = Config::getInstance();
}
$this->download = new VideoDownload();
$this->container = $container;
$this->view = $this->container->get('view');
$session_factory = new \Aura\Session\SessionFactory();
$session = $session_factory->newInstance($_COOKIE);
$session = $session_factory->newInstance($cookies);
$this->sessionSegment = $session->getSegment('Alltube\Controller\FrontController');
if ($this->config->stream) {
$this->defaultFormat = 'best';
@ -173,9 +179,9 @@ class FrontController
if ($this->config->stream) {
return $this->getStream($params['url'], 'mp3', $response, $request, $password);
} else {
$url = $this->download->getURL($params['url'], 'mp3[protocol^=http]', $password);
$urls = $this->download->getURL($params['url'], 'mp3[protocol^=http]', $password);
return $response->withRedirect($url);
return $response->withRedirect($urls[0]);
}
} catch (PasswordException $e) {
return $this->password($request, $response);
@ -218,18 +224,31 @@ class FrontController
} else {
$protocol = '[protocol^=http]';
}
if (isset($video->entries)) {
$template = 'playlist.tpl';
} else {
$template = 'video.tpl';
}
if (isset($video->title)) {
$title = $video->title;
$description = 'Download "'.$video->title.'" from '.$video->extractor_key;
} else {
$title = 'Video download';
$description = 'Download video from '.$video->extractor_key;
}
$this->view->render(
$response,
'video.tpl',
$template,
[
'video' => $video,
'class' => 'video',
'title' => $video->title,
'description' => 'Download "'.$video->title.'" from '.$video->extractor_key,
'title' => $title,
'description' => $description,
'protocol' => $protocol,
'config' => $this->config,
'canonical' => $this->getCanonicalUrl($request),
'uglyUrls' => $this->config->uglyUrls,
'remux' => $this->config->remux,
]
);
@ -298,10 +317,16 @@ class FrontController
*
* @return Response HTTP response
*/
private function getStream($url, $format, $response, $request, $password = null)
private function getStream($url, $format, Response $response, Request $request, $password = null)
{
$video = $this->download->getJSON($url, $format, $password);
if ($video->protocol == 'm3u8') {
if ($video->protocol == 'rtmp') {
$stream = $this->download->getRtmpStream($video);
$response = $response->withHeader('Content-Type', 'video/'.$video->ext);
if ($request->isGet()) {
$response = $response->withBody(new Stream($stream));
}
} elseif ($video->protocol == 'm3u8') {
$stream = $this->download->getM3uStream($video);
$response = $response->withHeader('Content-Type', 'video/'.$video->ext);
if ($request->isGet()) {
@ -316,11 +341,98 @@ class FrontController
$response = $response->withBody($stream->getBody());
}
}
$response = $response->withHeader('Content-Disposition', 'attachment; filename="'.$video->_filename.'"');
$response = $response->withHeader(
'Content-Disposition',
'attachment; filename="'.
$this->download->getFilename($url, $format, $password).'"'
);
return $response;
}
/**
* Get a remuxed stream piped through the server.
*
* @param array $urls URLs of the video and audio files
* @param string $format Requested format
* @param Response $response PSR-7 response
* @param Request $request PSR-7 request
*
* @return Response HTTP response
*/
private function getRemuxStream(array $urls, $format, Response $response, Request $request)
{
if (!$this->config->remux) {
throw new \Exception('You need to enable remux mode to merge two formats.');
}
$stream = $this->download->getRemuxStream($urls);
$response = $response->withHeader('Content-Type', 'video/x-matroska');
if ($request->isGet()) {
$response = $response->withBody(new Stream($stream));
}
$webpageUrl = $request->getQueryParam('url');
return $response->withHeader('Content-Disposition', 'attachment; filename="'.pathinfo(
$this->download->getFileNameWithExtension(
'mkv',
$webpageUrl,
$format,
$this->sessionSegment->getFlash($webpageUrl)
),
PATHINFO_FILENAME
).'.mkv"');
}
/**
* Get video format from request parameters or default format if none is specified.
*
* @param Request $request PSR-7 request
*
* @return string format
*/
private function getFormat(Request $request)
{
$format = $request->getQueryParam('format');
if (!isset($format)) {
$format = $this->defaultFormat;
}
return $format;
}
/**
* Get approriate HTTP response to redirect query
* Depends on whether we want to stream, remux or simply redirect.
*
* @param string $url URL of the video
* @param string $format Requested format
* @param Response $response PSR-7 response
* @param Request $request PSR-7 request
*
* @return Response HTTP response
*/
private function getRedirectResponse($url, $format, Response $response, Request $request)
{
$videoUrls = $this->download->getURL(
$url,
$format,
$this->sessionSegment->getFlash($url)
);
if (count($videoUrls) > 1) {
return $this->getRemuxStream($videoUrls, $format, $response, $request);
} elseif ($this->config->stream) {
return $this->getStream(
$url,
$format,
$response,
$request,
$this->sessionSegment->getFlash($url)
);
} else {
return $response->withRedirect($videoUrls[0]);
}
}
/**
* Redirect to video file.
*
@ -331,34 +443,14 @@ 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'])) {
$url = $request->getQueryParam('url');
$format = $this->getFormat($request);
if (isset($url)) {
try {
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 $this->getRedirectResponse($url, $format, $response, $request);
} catch (PasswordException $e) {
return $response->withRedirect(
$this->container->get('router')->pathFor('video').'?url='.urlencode($params['url'])
$this->container->get('router')->pathFor('video').'?url='.urlencode($url)
);
} catch (\Exception $e) {
$response->getBody()->write($e->getMessage());

View file

@ -397,10 +397,35 @@ padding:3px;
/* Playlists */
.playlist-entry .thumb {
float: left;
margin-right: 1em;
}
.playlist-entry {
clear: both;
padding-top: 2em;
text-align: left;
width: 600px;
}
.playlist-entry h3 {
margin-top: 0;
}
.playlist-entry h3 a {
text-decoration: none;
}
.playlist-entry h3 a:hover {
text-decoration: underline;
}
.playlist-entry .downloadBtn {
font-size: 16px;
border-width: 2px;
}
@ -658,6 +683,16 @@ h1 {
text-align:left;
}
.playlist-entry {
text-align: center;
width: auto;
}
.playlist-entry .thumb {
float: none;
margin-right: 0;
}
}
@media all and (display-mode: standalone) {

View file

@ -4,31 +4,23 @@ require_once __DIR__.'/vendor/autoload.php';
use Alltube\Config;
use Alltube\Controller\FrontController;
use Alltube\UglyRouter;
use Alltube\ViewFactory;
use Slim\App;
if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/index.php') !== false) {
header('Location: '.str_ireplace('/index.php', '/', $_SERVER['REQUEST_URI']));
die;
}
$app = new \Slim\App();
$app = new App();
$container = $app->getContainer();
$config = Config::getInstance();
if ($config->uglyUrls) {
$container['router'] = new UglyRouter();
}
$container['view'] = function ($c) {
$view = new \Slim\Views\Smarty(__DIR__.'/templates/');
$container['view'] = ViewFactory::create($container);
$smartyPlugins = new \Slim\Views\SmartyPlugins($c['router'], $c['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;
};
$controller = new FrontController($container);
$controller = new FrontController($container, null, $_COOKIE);
$container['errorHandler'] = [$controller, 'error'];

View file

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

View file

@ -1,7 +1,7 @@
{
"name": "alltube",
"description": "HTML GUI for youtube-dl",
"version": "0.8.1-beta",
"version": "0.9.0",
"author": "Pierre Rudloff",
"bugs": "https://github.com/Rudloff/alltube/issues",
"dependencies": {

29
templates/playlist.tpl Normal file
View file

@ -0,0 +1,29 @@
{include file="inc/head.tpl"}
<div class="wrapper">
<div class="main">
{include file="inc/logo.tpl"}
<p>Videos extracted from the {if isset($video->title)}<i>
<a href="{$video->webpage_url}">
{$video->title}</a></i>{/if} playlist:
</p>
{foreach $video->entries as $video}
<div class="playlist-entry">
<h3><a target="_blank" href="{strip}
{if isset($video->ie_key) and $video->ie_key == Youtube and !filter_var($video->url, FILTER_VALIDATE_URL)}
https://www.youtube.com/watch?v=
{/if}
{$video->url}
{/strip}">
{if !isset($video->title) and $video->ie_key == YoutubePlaylist}
Playlist
{else}
{$video->title}
{/if}
</a></h3>
<a target="_blank" class="downloadBtn" href="{path_for name="redirect"}?url={$video->url}">Download</a>
<a target="_blank" href="{path_for name="video"}?url={$video->url}">More options</a>
</div>
{/foreach}
</div>
{include file="inc/footer.tpl"}

View file

@ -34,6 +34,11 @@
Best ({$video->ext})
{/strip}
</option>
{if $remux}
<option value="bestvideo+bestaudio">
Remux best video with best audio
</option>
{/if}
<option value="worst{$protocol}">
Worst
</option>

View file

@ -45,7 +45,6 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
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);
@ -64,6 +63,16 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
Config::getInstance('foo');
}
/**
* Test the getInstance function with aen empty filename.
*
* @return void
*/
public function testGetInstanceWithEmptyFile()
{
Config::getInstance('');
}
/**
* Test the getInstance function with the CONVERT and PYTHON environment variables.
*

View file

@ -7,6 +7,7 @@ namespace Alltube\Test;
use Alltube\Config;
use Alltube\Controller\FrontController;
use Alltube\ViewFactory;
use Slim\Container;
use Slim\Http\Environment;
use Slim\Http\Request;
@ -53,18 +54,8 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
$this->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['view'] = ViewFactory::create($this->container, $this->request);
$this->controller = new FrontController($this->container, Config::getInstance('config_test.yml'));
$this->container['router']->map(['GET'], '/', [$this->controller, 'index'])
->setName('index');
$this->container['router']->map(['GET'], '/video', [$this->controller, 'video'])
@ -83,6 +74,82 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
Config::destroyInstance();
}
/**
* Run controller function with custom query parameters and return the result.
*
* @param string $request Controller function to call
* @param array $params Query parameters
* @param Config $config Custom config
*
* @return Response HTTP response
*/
private function getRequestResult($request, array $params, Config $config = null)
{
if (isset($config)) {
$controller = new FrontController($this->container, $config);
} else {
$controller = $this->controller;
}
return $controller->$request(
$this->request->withQueryParams($params),
$this->response
);
}
/**
* Assert that calling controller function with these parameters returns a 200 HTTP response.
*
* @param string $request Controller function to call
* @param array $params Query parameters
* @param Config $config Custom config
*
* @return void
*/
private function assertRequestIsOk($request, array $params = [], Config $config = null)
{
$this->assertTrue($this->getRequestResult($request, $params, $config)->isOk());
}
/**
* Assert that calling controller function with these parameters returns an HTTP redirect.
*
* @param string $request Controller function to call
* @param array $params Query parameters
* @param Config $config Custom config
*
* @return void
*/
private function assertRequestIsRedirect($request, array $params = [], Config $config = null)
{
$this->assertTrue($this->getRequestResult($request, $params, $config)->isRedirect());
}
/**
* Assert that calling controller function with these parameters returns an HTTP redirect.
*
* @param string $request Controller function to call
* @param array $params Query parameters
* @param Config $config Custom config
*
* @return void
*/
private function assertRequestIsServerError($request, array $params = [], Config $config = null)
{
$this->assertTrue($this->getRequestResult($request, $params, $config)->isServerError());
}
/**
* Test the constructor.
*
* @return void
*/
public function testConstructor()
{
$controller = new FrontController($this->container);
$this->assertInstanceOf(FrontController::class, $controller);
}
/**
* Test the constructor with streams enabled.
*
@ -90,9 +157,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testConstructorWithStream()
{
$config = Config::getInstance();
$config->stream = true;
$controller = new FrontController($this->container);
$controller = new FrontController($this->container, new Config(['stream'=>true]));
$this->assertInstanceOf(FrontController::class, $controller);
}
@ -103,8 +168,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testIndex()
{
$result = $this->controller->index($this->request, $this->response);
$this->assertTrue($result->isOk());
$this->assertRequestIsOk('index');
}
/**
@ -130,8 +194,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testExtractors()
{
$result = $this->controller->extractors($this->request, $this->response);
$this->assertTrue($result->isOk());
$this->assertRequestIsOk('extractors');
}
/**
@ -141,8 +204,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testPassword()
{
$result = $this->controller->password($this->request, $this->response);
$this->assertTrue($result->isOk());
$this->assertRequestIsOk('password');
}
/**
@ -152,8 +214,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testVideoWithoutUrl()
{
$result = $this->controller->video($this->request, $this->response);
$this->assertTrue($result->isRedirect());
$this->assertRequestIsRedirect('video');
}
/**
@ -163,11 +224,17 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testVideo()
{
$result = $this->controller->video(
$this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']),
$this->response
);
$this->assertTrue($result->isOk());
$this->assertRequestIsOk('video', ['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']);
}
/**
* Test the video() function with a video that does not have a title.
*
* @return void
*/
public function testVideoWithoutTitle()
{
$this->assertRequestIsOk('video', ['url'=>'http://html5demos.com/video']);
}
/**
@ -177,11 +244,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
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());
$this->assertRequestIsOk('video', ['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'audio'=>true]);
}
/**
@ -191,14 +254,10 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testVideoWithUnconvertedAudio()
{
$result = $this->controller->video(
$this->request->withQueryParams(
['url' => 'https://2080.bandcamp.com/track/cygnus-x-the-orange-theme-2080-faulty-chip-cover',
'audio'=> true, ]
),
$this->response
$this->assertRequestIsRedirect(
'video',
['url'=> 'https://2080.bandcamp.com/track/cygnus-x-the-orange-theme-2080-faulty-chip-cover', 'audio'=>true]
);
$this->assertTrue($result->isRedirect());
}
/**
@ -223,16 +282,8 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
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());
$this->assertRequestIsOk('video', ['url'=>'http://vimeo.com/68375962']);
$this->assertRequestIsOk('video', ['url'=>'http://vimeo.com/68375962', 'audio'=>true]);
}
/**
@ -242,18 +293,26 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
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
$config = new Config(['stream'=>true]);
$this->assertRequestIsOk('video', ['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU'], $config);
$this->assertRequestIsOk(
'video',
['url'=> 'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'audio'=>true],
$config
);
$this->assertTrue($result->isOk());
$result = $this->controller->video(
$this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'audio'=>true]),
$this->response
}
/**
* Test the video() function with a playlist.
*
* @return void
*/
public function testVideoWithPlaylist()
{
$this->assertRequestIsOk(
'video',
['url'=> 'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC']
);
$this->assertTrue($result->isOk());
}
/**
@ -274,8 +333,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testRedirectWithoutUrl()
{
$result = $this->controller->redirect($this->request, $this->response);
$this->assertTrue($result->isRedirect());
$this->assertRequestIsRedirect('redirect');
}
/**
@ -285,11 +343,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testRedirect()
{
$result = $this->controller->redirect(
$this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']),
$this->response
);
$this->assertTrue($result->isRedirect());
$this->assertRequestIsRedirect('redirect', ['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']);
}
/**
@ -299,11 +353,10 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testRedirectWithFormat()
{
$result = $this->controller->redirect(
$this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'format'=>'worst']),
$this->response
$this->assertRequestIsRedirect(
'redirect',
['url'=> 'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'format'=>'worst']
);
$this->assertTrue($result->isRedirect());
}
/**
@ -313,13 +366,11 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
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->assertRequestIsOk(
'redirect',
['url'=> 'https://www.youtube.com/watch?v=M7IpKCZ47pU'],
new Config(['stream'=>true])
);
$this->assertTrue($result->isOk());
}
/**
@ -329,15 +380,58 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
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->assertRequestIsOk(
'redirect',
['url'=> 'https://twitter.com/verge/status/813055465324056576/video/1'],
new Config(['stream'=>true])
);
}
/**
* Test the redirect() function with an RTMP stream.
*
* @return void
*/
public function testRedirectWithRtmpStream()
{
$this->assertRequestIsOk(
'redirect',
['url'=> 'http://www.rtl2.de/sendung/grip-das-motormagazin/folge/folge-203-0'],
new Config(['stream'=>true])
);
}
/**
* Test the redirect() function with a remuxed video.
*
* @return void
*/
public function testRedirectWithRemux()
{
$this->assertRequestIsOk(
'redirect',
[
'url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU',
'format'=> 'bestvideo+bestaudio',
],
new Config(['remux'=>true])
);
}
/**
* Test the redirect() function with a remuxed video but remux disabled.
*
* @return void
*/
public function testRedirectWithRemuxDisabled()
{
$this->assertRequestIsServerError(
'redirect',
[
'url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU',
'format'=> 'bestvideo+bestaudio',
]
);
$this->assertTrue($result->isOk());
}
/**
@ -347,11 +441,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testRedirectWithMissingPassword()
{
$result = $this->controller->redirect(
$this->request->withQueryParams(['url'=>'http://vimeo.com/68375962']),
$this->response
);
$this->assertTrue($result->isRedirect());
$this->assertRequestIsRedirect('redirect', ['url'=>'http://vimeo.com/68375962']);
}
/**
@ -361,10 +451,6 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testRedirectWithError()
{
$result = $this->controller->redirect(
$this->request->withQueryParams(['url'=>'http://example.com/foo']),
$this->response
);
$this->assertTrue($result->isServerError());
$this->assertRequestIsServerError('redirect', ['url'=>'http://example.com/foo']);
}
}

View file

@ -25,7 +25,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
protected function setUp()
{
$this->download = new VideoDownload();
$this->download = new VideoDownload(Config::getInstance('config_test.yml'));
}
/**
@ -84,11 +84,14 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*
* @return void
* @dataProvider urlProvider
* @dataProvider m3uUrlProvider
* @dataProvider rtmpUrlProvider
* @dataProvider remuxUrlProvider
*/
public function testGetURL($url, $format, $filename, $extension, $domain)
{
$videoURL = $this->download->getURL($url, $format);
$this->assertContains($domain, $videoURL);
$this->assertContains($domain, $videoURL[0]);
}
/**
@ -98,7 +101,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
public function testGetURLWithPassword()
{
$this->assertContains('vimeocdn.com', $this->download->getURL('http://vimeo.com/68375962', null, 'youtube-dl'));
$videoURL = $this->download->getURL('http://vimeo.com/68375962', null, 'youtube-dl');
$this->assertContains('vimeocdn.com', $videoURL[0]);
}
/**
@ -184,6 +188,23 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*
* @return array[]
*/
public function remuxUrlProvider()
{
return [
[
'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'bestvideo+bestaudio',
"It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU",
'mp4',
'googlevideo.com',
],
];
}
/**
* Provides URLs for remux tests.
*
* @return array[]
*/
public function m3uUrlProvider()
{
return [
@ -196,6 +217,23 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
];
}
/**
* Provides RTMP URLs for tests.
*
* @return array[]
*/
public function rtmpUrlProvider()
{
return [
[
'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',
],
];
}
/**
* Provides incorrect URLs for tests.
*
@ -215,8 +253,9 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
* @param string $format Format
*
* @return void
* @dataProvider URLProvider
* @dataProvider urlProvider
* @dataProvider m3uUrlProvider
* @dataProvider rtmpUrlProvider
*/
public function testGetJSON($url, $format)
{
@ -227,7 +266,6 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
$this->assertObjectHasAttribute('title', $info);
$this->assertObjectHasAttribute('extractor_key', $info);
$this->assertObjectHasAttribute('formats', $info);
$this->assertObjectHasAttribute('_filename', $info);
}
/**
@ -255,6 +293,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
* @return void
* @dataProvider urlProvider
* @dataProvider m3uUrlProvider
* @dataProvider rtmpUrlProvider
* @dataProvider remuxUrlProvider
*/
public function testGetFilename($url, $format, $filename, $extension)
{
@ -286,6 +326,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
* @return void
* @dataProvider urlProvider
* @dataProvider m3uUrlProvider
* @dataProvider rtmpUrlProvider
* @dataProvider remuxUrlProvider
*/
public function testGetAudioFilename($url, $format, $filename)
{
@ -321,9 +363,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
public function testGetAudioStreamAvconvError($url, $format)
{
$config = Config::getInstance();
$config->avconv = 'foobar';
$this->download->getAudioStream($url, $format);
$download = new VideoDownload(new Config(['avconv'=>'foobar']));
$download->getAudioStream($url, $format);
}
/**
@ -334,14 +375,12 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*
* @return void
* @expectedException Exception
* @dataProvider urlProvider
* @dataProvider rtmpUrlProvider
*/
public function testGetAudioStreamCurlError($url, $format)
public function testGetAudioStreamRtmpError($url, $format)
{
$config = Config::getInstance();
$config->curl = 'foobar';
$config->rtmpdump = 'foobar';
$this->download->getAudioStream($url, $format);
$download = new VideoDownload(new Config(['rtmpdump'=>'foobar']));
$download->getAudioStream($url, $format);
}
/**
@ -359,6 +398,19 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
$this->download->getAudioStream($url, $format);
}
/**
* Assert that a stream is valid.
*
* @param resource $stream Stream
*
* @return void
*/
private function assertStream($stream)
{
$this->assertInternalType('resource', $stream);
$this->assertFalse(feof($stream));
}
/**
* Test getM3uStream function.
*
@ -370,10 +422,46 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
public function testGetM3uStream($url, $format)
{
$video = $this->download->getJSON($url, $format);
$stream = $this->download->getM3uStream($video);
$this->assertInternalType('resource', $stream);
$this->assertFalse(feof($stream));
$this->assertStream(
$this->download->getM3uStream(
$this->download->getJSON($url, $format)
)
);
}
/**
* Test getRemuxStream function.
*
* @param string $url URL
* @param string $format Format
*
* @return void
* @dataProvider remuxUrlProvider
*/
public function testGetRemuxStream($url, $format)
{
$urls = $this->download->getURL($url, $format);
if (count($urls) > 1) {
$this->assertStream($this->download->getRemuxStream($urls));
}
}
/**
* Test getRtmpStream function.
*
* @param string $url URL
* @param string $format Format
*
* @return void
* @dataProvider rtmpUrlProvider
*/
public function testGetRtmpStream($url, $format)
{
$this->assertStream(
$this->download->getRtmpStream(
$this->download->getJSON($url, $format)
)
);
}
/**
@ -388,9 +476,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
public function testGetM3uStreamAvconvError($url, $format)
{
$config = \Alltube\Config::getInstance();
$config->avconv = 'foobar';
$video = $this->download->getJSON($url, $format);
$this->download->getM3uStream($video);
$download = new VideoDownload(new Config(['avconv'=>'foobar']));
$video = $download->getJSON($url, $format);
$download->getM3uStream($video);
}
}

27
tests/ViewFactoryTest.php Normal file
View file

@ -0,0 +1,27 @@
<?php
/**
* ViewFactoryTest class.
*/
namespace Alltube\Test;
use Alltube\ViewFactory;
use Slim\Container;
use Slim\Views\Smarty;
/**
* Unit tests for the ViewFactory class.
*/
class ViewFactoryTest extends \PHPUnit_Framework_TestCase
{
/**
* Test the create() function.
*
* @return void
*/
public function testCreate()
{
$view = ViewFactory::create(new Container());
$this->assertInstanceOf(Smarty::class, $view);
}
}