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 * `youtubedl`: path to your youtube-dl binary
* `python`: path to your python binary * `python`: path to your python binary
* `params`: an array of parameters to pass to youtube-dl * `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 * `convert`: true to enable audio conversion
* `avconv`: path to your avconv or ffmpeg binary * `avconv`: path to your avconv or ffmpeg binary
* `rtmpdump`: path to your rtmpdump binary * `rtmpdump`: path to your rtmpdump binary
* `remux`: enable remux mode (experimental)
See [`config.example.yml`](config.example.yml) for default values. See [`config.example.yml`](config.example.yml) for default values.
@ -31,10 +31,10 @@ convert: true
avconv: path/to/avconv 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 ```bash
sudo apt-get install libav-tools curl sudo apt-get install libav-tools
``` ```
## How do I deploy Alltube on Heroku? ## 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 ```bash
chmod 0667 /tmp/heroku.fcgi.5000.sock 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 ## 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`. If you don't want to enable conversions, you can disable it in `config.yml`.
On Debian-based systems: On Debian-based systems:
```bash ```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). 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 * @var array
*/ */
public $params = ['--no-playlist', '--no-warnings', '--playlist-end', 1]; public $params = ['--no-warnings', '--ignore-errors', '--flat-playlist'];
/** /**
* Enable audio conversion. * Enable audio conversion.
@ -61,20 +61,6 @@ class Config
*/ */
public $rtmpdump = 'vendor/bin/rtmpdump'; public $rtmpdump = 'vendor/bin/rtmpdump';
/**
* curl binary path.
*
* @var string
*/
public $curl = '/usr/bin/curl';
/**
* curl parameters.
*
* @var array
*/
public $curl_params = [];
/** /**
* Disable URL rewriting. * Disable URL rewriting.
* *
@ -89,6 +75,13 @@ class Config
*/ */
public $stream = false; public $stream = false;
/**
* Allow to remux video + audio?
*
* @var bool
*/
public $remux = false;
/** /**
* YAML config file path. * YAML config file path.
* *
@ -104,9 +97,7 @@ class Config
* * python: Python binary path * * python: Python binary path
* * avconv: avconv or ffmpeg binary path * * avconv: avconv or ffmpeg binary path
* * rtmpdump: rtmpdump binary path * * rtmpdump: rtmpdump binary path
* * curl: curl binary path
* * params: Array of youtube-dl parameters * * params: Array of youtube-dl parameters
* * curl_params: Array of curl parameters
* * convert: Enable conversion? * * convert: Enable conversion?
* *
* @param array $options Options * @param array $options Options
@ -141,7 +132,7 @@ class Config
if (is_null(self::$instance) || self::$instance->file != $yamlfile) { if (is_null(self::$instance) || self::$instance->file != $yamlfile) {
if (is_file($yamlfile)) { if (is_file($yamlfile)) {
$options = Yaml::parse(file_get_contents($yamlPath)); $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 Allow for the default file to be missing in order to
not surprise users that did not create a config file not surprise users that did not create a config file

View file

@ -98,7 +98,7 @@ class VideoDownload
throw new \Exception($errorOutput); throw new \Exception($errorOutput);
} }
} else { } else {
return $process->getOutput(); return trim($process->getOutput());
} }
} }
@ -113,21 +113,25 @@ class VideoDownload
* */ * */
public function getJSON($url, $format = null, $password = null) 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. * 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 $url URL of page
* @param string $format Format to use for the video * @param string $format Format to use for the video
* @param string $password Video password * @param string $password Video password
* *
* @return string URL of video * @return string[] URLs of video
* */ * */
public function getURL($url, $format = null, $password = null) 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)); 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. * Get filename of audio from URL of page.
* *
@ -155,14 +181,7 @@ class VideoDownload
* */ * */
public function getAudioFilename($url, $format = null, $password = null) public function getAudioFilename($url, $format = null, $password = null)
{ {
return html_entity_decode( return $this->getFileNameWithExtension('mp3', $url, $format, $password);
pathinfo(
$this->getFilename($url, $format, $password),
PATHINFO_FILENAME
).'.mp3',
ENT_COMPAT,
'ISO-8859-1'
);
} }
/** /**
@ -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 * @return \Symfony\Component\Process\Process Process
*/ */
private function getCurlProcess($video) private function getAvconvMp3Process($url)
{ {
if (!shell_exec('which '.$this->config->curl)) { if (!shell_exec('which '.$this->config->avconv)) {
throw(new \Exception('Can\'t find curl')); 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) 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); $video = $this->getJSON($url, $format, $password);
if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) { if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) {
throw(new \Exception('Conversion of M3U8 files is not supported.')); 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') { if (parse_url($video->url, PHP_URL_SCHEME) == 'rtmp') {
$process = $this->getRtmpProcess($video); $process = $this->getRtmpProcess($video);
} else { $chain = new Chain($process);
$process = $this->getCurlProcess($video); $chain->add('|', $this->getAvconvMp3Process('-'));
}
$chain = new Chain($process);
$chain->add('|', $avconvProc);
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'); 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", "type": "project",
"require": { "require": {
"smarty/smarty": "~3.1.29", "smarty/smarty": "~3.1.29",
"slim/slim": "~3.7.0", "slim/slim": "~3.8.1",
"mathmarques/smarty-view": "~1.1.0", "mathmarques/smarty-view": "~1.1.0",
"symfony/yaml": "~3.2.0", "symfony/yaml": "~3.2.0",
"symfony/process": "~3.2.0", "symfony/process": "~3.2.0",
@ -21,7 +21,7 @@
"squizlabs/php_codesniffer": "~2.8.0", "squizlabs/php_codesniffer": "~2.8.0",
"phpunit/phpunit": "~5.7.2", "phpunit/phpunit": "~5.7.2",
"ffmpeg/ffmpeg": "dev-release", "ffmpeg/ffmpeg": "dev-release",
"rg3/youtube-dl": "~2017.04.15", "rg3/youtube-dl": "~2017.04.28",
"rudloff/rtmpdump-bin": "~2.3", "rudloff/rtmpdump-bin": "~2.3",
"heroku/heroku-buildpack-php": "*" "heroku/heroku-buildpack-php": "*"
}, },
@ -37,10 +37,10 @@
"type": "package", "type": "package",
"package": { "package": {
"name": "rg3/youtube-dl", "name": "rg3/youtube-dl",
"version": "2017.04.15", "version": "2017.04.28",
"dist": { "dist": {
"type": "zip", "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", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "863f0c9fafcc85d185aa4441b0ad7e71", "content-hash": "6fda75dec4d72b9620ca68697278dc7e",
"packages": [ "packages": [
{ {
"name": "aura/session", "name": "aura/session",
@ -752,23 +752,24 @@
}, },
{ {
"name": "slim/slim", "name": "slim/slim",
"version": "3.7.0", "version": "3.8.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/slimphp/Slim.git", "url": "https://github.com/slimphp/Slim.git",
"reference": "4254e40d81559e35cdf856bcbaca5f3af468b7ef" "reference": "5385302707530b2bccee1769613ad769859b826d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/4254e40d81559e35cdf856bcbaca5f3af468b7ef", "url": "https://api.github.com/repos/slimphp/Slim/zipball/5385302707530b2bccee1769613ad769859b826d",
"reference": "4254e40d81559e35cdf856bcbaca5f3af468b7ef", "reference": "5385302707530b2bccee1769613ad769859b826d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"container-interop/container-interop": "^1.1", "container-interop/container-interop": "^1.2",
"nikic/fast-route": "^1.0", "nikic/fast-route": "^1.0",
"php": ">=5.5.0", "php": ">=5.5.0",
"pimple/pimple": "^3.0", "pimple/pimple": "^3.0",
"psr/container": "^1.0",
"psr/http-message": "^1.0" "psr/http-message": "^1.0"
}, },
"provide": { "provide": {
@ -818,7 +819,7 @@
"micro", "micro",
"router" "router"
], ],
"time": "2016-12-20T20:30:47+00:00" "time": "2017-03-19T17:55:20+00:00"
}, },
{ {
"name": "smarty/smarty", "name": "smarty/smarty",
@ -1734,10 +1735,10 @@
}, },
{ {
"name": "rg3/youtube-dl", "name": "rg3/youtube-dl",
"version": "2017.04.15", "version": "2017.04.28",
"dist": { "dist": {
"type": "zip", "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, "reference": null,
"shasum": null "shasum": null
}, },

View file

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

View file

@ -8,7 +8,7 @@ namespace Alltube\Controller;
use Alltube\Config; use Alltube\Config;
use Alltube\PasswordException; use Alltube\PasswordException;
use Alltube\VideoDownload; use Alltube\VideoDownload;
use Interop\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Slim\Container; use Slim\Container;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -65,15 +65,21 @@ class FrontController
* FrontController constructor. * FrontController constructor.
* *
* @param Container $container Slim dependency container * @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->download = new VideoDownload();
$this->container = $container; $this->container = $container;
$this->view = $this->container->get('view'); $this->view = $this->container->get('view');
$session_factory = new \Aura\Session\SessionFactory(); $session_factory = new \Aura\Session\SessionFactory();
$session = $session_factory->newInstance($_COOKIE); $session = $session_factory->newInstance($cookies);
$this->sessionSegment = $session->getSegment('Alltube\Controller\FrontController'); $this->sessionSegment = $session->getSegment('Alltube\Controller\FrontController');
if ($this->config->stream) { if ($this->config->stream) {
$this->defaultFormat = 'best'; $this->defaultFormat = 'best';
@ -173,9 +179,9 @@ class FrontController
if ($this->config->stream) { if ($this->config->stream) {
return $this->getStream($params['url'], 'mp3', $response, $request, $password); return $this->getStream($params['url'], 'mp3', $response, $request, $password);
} else { } 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) { } catch (PasswordException $e) {
return $this->password($request, $response); return $this->password($request, $response);
@ -218,18 +224,31 @@ class FrontController
} else { } else {
$protocol = '[protocol^=http]'; $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( $this->view->render(
$response, $response,
'video.tpl', $template,
[ [
'video' => $video, 'video' => $video,
'class' => 'video', 'class' => 'video',
'title' => $video->title, 'title' => $title,
'description' => 'Download "'.$video->title.'" from '.$video->extractor_key, 'description' => $description,
'protocol' => $protocol, 'protocol' => $protocol,
'config' => $this->config, 'config' => $this->config,
'canonical' => $this->getCanonicalUrl($request), 'canonical' => $this->getCanonicalUrl($request),
'uglyUrls' => $this->config->uglyUrls, 'uglyUrls' => $this->config->uglyUrls,
'remux' => $this->config->remux,
] ]
); );
@ -298,10 +317,16 @@ class FrontController
* *
* @return Response HTTP response * @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); $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); $stream = $this->download->getM3uStream($video);
$response = $response->withHeader('Content-Type', 'video/'.$video->ext); $response = $response->withHeader('Content-Type', 'video/'.$video->ext);
if ($request->isGet()) { if ($request->isGet()) {
@ -316,11 +341,98 @@ class FrontController
$response = $response->withBody($stream->getBody()); $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; 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. * Redirect to video file.
* *
@ -331,34 +443,14 @@ class FrontController
*/ */
public function redirect(Request $request, Response $response) public function redirect(Request $request, Response $response)
{ {
$params = $request->getQueryParams(); $url = $request->getQueryParam('url');
if (isset($params['format'])) { $format = $this->getFormat($request);
$format = $params['format']; if (isset($url)) {
} else {
$format = $this->defaultFormat;
}
if (isset($params['url'])) {
try { try {
if ($this->config->stream) { return $this->getRedirectResponse($url, $format, $response, $request);
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);
}
} catch (PasswordException $e) { } catch (PasswordException $e) {
return $response->withRedirect( 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) { } catch (\Exception $e) {
$response->getBody()->write($e->getMessage()); $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; text-align:left;
} }
.playlist-entry {
text-align: center;
width: auto;
}
.playlist-entry .thumb {
float: none;
margin-right: 0;
}
} }
@media all and (display-mode: standalone) { @media all and (display-mode: standalone) {

View file

@ -4,31 +4,23 @@ require_once __DIR__.'/vendor/autoload.php';
use Alltube\Config; use Alltube\Config;
use Alltube\Controller\FrontController; use Alltube\Controller\FrontController;
use Alltube\UglyRouter; use Alltube\UglyRouter;
use Alltube\ViewFactory;
use Slim\App;
if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/index.php') !== false) { if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/index.php') !== false) {
header('Location: '.str_ireplace('/index.php', '/', $_SERVER['REQUEST_URI'])); header('Location: '.str_ireplace('/index.php', '/', $_SERVER['REQUEST_URI']));
die; die;
} }
$app = new \Slim\App(); $app = new App();
$container = $app->getContainer(); $container = $app->getContainer();
$config = Config::getInstance(); $config = Config::getInstance();
if ($config->uglyUrls) { if ($config->uglyUrls) {
$container['router'] = new UglyRouter(); $container['router'] = new UglyRouter();
} }
$container['view'] = function ($c) { $container['view'] = ViewFactory::create($container);
$view = new \Slim\Views\Smarty(__DIR__.'/templates/');
$smartyPlugins = new \Slim\Views\SmartyPlugins($c['router'], $c['request']->getUri()); $controller = new FrontController($container, null, $_COOKIE);
$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);
$container['errorHandler'] = [$controller, 'error']; $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", "name": "alltube",
"description": "HTML GUI for youtube-dl", "description": "HTML GUI for youtube-dl",
"version": "0.8.1-beta", "version": "0.9.0",
"author": "Pierre Rudloff", "author": "Pierre Rudloff",
"bugs": "https://github.com/Rudloff/alltube/issues", "bugs": "https://github.com/Rudloff/alltube/issues",
"dependencies": { "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}) Best ({$video->ext})
{/strip} {/strip}
</option> </option>
{if $remux}
<option value="bestvideo+bestaudio">
Remux best video with best audio
</option>
{/if}
<option value="worst{$protocol}"> <option value="worst{$protocol}">
Worst Worst
</option> </option>

View file

@ -45,7 +45,6 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
public function testGetInstance() public function testGetInstance()
{ {
$this->assertEquals($this->config->convert, false); $this->assertEquals($this->config->convert, false);
$this->assertInternalType('array', $this->config->curl_params);
$this->assertInternalType('array', $this->config->params); $this->assertInternalType('array', $this->config->params);
$this->assertInternalType('string', $this->config->youtubedl); $this->assertInternalType('string', $this->config->youtubedl);
$this->assertInternalType('string', $this->config->python); $this->assertInternalType('string', $this->config->python);
@ -64,6 +63,16 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
Config::getInstance('foo'); 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. * 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\Config;
use Alltube\Controller\FrontController; use Alltube\Controller\FrontController;
use Alltube\ViewFactory;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
@ -53,18 +54,8 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
$this->container = new Container(); $this->container = new Container();
$this->request = Request::createFromEnvironment(Environment::mock()); $this->request = Request::createFromEnvironment(Environment::mock());
$this->response = new Response(); $this->response = new Response();
$this->container['view'] = function ($c) { $this->container['view'] = ViewFactory::create($this->container, $this->request);
$view = new \Slim\Views\Smarty(__DIR__.'/../templates/'); $this->controller = new FrontController($this->container, Config::getInstance('config_test.yml'));
$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']) $this->container['router']->map(['GET'], '/', [$this->controller, 'index'])
->setName('index'); ->setName('index');
$this->container['router']->map(['GET'], '/video', [$this->controller, 'video']) $this->container['router']->map(['GET'], '/video', [$this->controller, 'video'])
@ -83,6 +74,82 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
Config::destroyInstance(); 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. * Test the constructor with streams enabled.
* *
@ -90,9 +157,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testConstructorWithStream() public function testConstructorWithStream()
{ {
$config = Config::getInstance(); $controller = new FrontController($this->container, new Config(['stream'=>true]));
$config->stream = true;
$controller = new FrontController($this->container);
$this->assertInstanceOf(FrontController::class, $controller); $this->assertInstanceOf(FrontController::class, $controller);
} }
@ -103,8 +168,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testIndex() public function testIndex()
{ {
$result = $this->controller->index($this->request, $this->response); $this->assertRequestIsOk('index');
$this->assertTrue($result->isOk());
} }
/** /**
@ -130,8 +194,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testExtractors() public function testExtractors()
{ {
$result = $this->controller->extractors($this->request, $this->response); $this->assertRequestIsOk('extractors');
$this->assertTrue($result->isOk());
} }
/** /**
@ -141,8 +204,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testPassword() public function testPassword()
{ {
$result = $this->controller->password($this->request, $this->response); $this->assertRequestIsOk('password');
$this->assertTrue($result->isOk());
} }
/** /**
@ -152,8 +214,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testVideoWithoutUrl() public function testVideoWithoutUrl()
{ {
$result = $this->controller->video($this->request, $this->response); $this->assertRequestIsRedirect('video');
$this->assertTrue($result->isRedirect());
} }
/** /**
@ -163,11 +224,17 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testVideo() public function testVideo()
{ {
$result = $this->controller->video( $this->assertRequestIsOk('video', ['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']);
$this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']), }
$this->response
); /**
$this->assertTrue($result->isOk()); * 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() public function testVideoWithAudio()
{ {
$result = $this->controller->video( $this->assertRequestIsOk('video', ['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'audio'=>true]);
$this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'audio'=>true]),
$this->response
);
$this->assertTrue($result->isOk());
} }
/** /**
@ -191,14 +254,10 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testVideoWithUnconvertedAudio() public function testVideoWithUnconvertedAudio()
{ {
$result = $this->controller->video( $this->assertRequestIsRedirect(
$this->request->withQueryParams( 'video',
['url' => 'https://2080.bandcamp.com/track/cygnus-x-the-orange-theme-2080-faulty-chip-cover', ['url'=> 'https://2080.bandcamp.com/track/cygnus-x-the-orange-theme-2080-faulty-chip-cover', 'audio'=>true]
'audio'=> true, ]
),
$this->response
); );
$this->assertTrue($result->isRedirect());
} }
/** /**
@ -223,16 +282,8 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testVideoWithMissingPassword() public function testVideoWithMissingPassword()
{ {
$result = $this->controller->video( $this->assertRequestIsOk('video', ['url'=>'http://vimeo.com/68375962']);
$this->request->withQueryParams(['url'=>'http://vimeo.com/68375962']), $this->assertRequestIsOk('video', ['url'=>'http://vimeo.com/68375962', 'audio'=>true]);
$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());
} }
/** /**
@ -242,18 +293,26 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testVideoWithStream() public function testVideoWithStream()
{ {
$config = Config::getInstance(); $config = new Config(['stream'=>true]);
$config->stream = true; $this->assertRequestIsOk('video', ['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU'], $config);
$result = $this->controller->video( $this->assertRequestIsOk(
$this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']), 'video',
$this->response ['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() public function testRedirectWithoutUrl()
{ {
$result = $this->controller->redirect($this->request, $this->response); $this->assertRequestIsRedirect('redirect');
$this->assertTrue($result->isRedirect());
} }
/** /**
@ -285,11 +343,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testRedirect() public function testRedirect()
{ {
$result = $this->controller->redirect( $this->assertRequestIsRedirect('redirect', ['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']);
$this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']),
$this->response
);
$this->assertTrue($result->isRedirect());
} }
/** /**
@ -299,11 +353,10 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testRedirectWithFormat() public function testRedirectWithFormat()
{ {
$result = $this->controller->redirect( $this->assertRequestIsRedirect(
$this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'format'=>'worst']), 'redirect',
$this->response ['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() public function testRedirectWithStream()
{ {
$config = Config::getInstance(); $this->assertRequestIsOk(
$config->stream = true; 'redirect',
$result = $this->controller->redirect( ['url'=> 'https://www.youtube.com/watch?v=M7IpKCZ47pU'],
$this->request->withQueryParams(['url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU']), new Config(['stream'=>true])
$this->response
); );
$this->assertTrue($result->isOk());
} }
/** /**
@ -329,15 +380,58 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testRedirectWithM3uStream() public function testRedirectWithM3uStream()
{ {
$config = Config::getInstance(); $this->assertRequestIsOk(
$config->stream = true; 'redirect',
//We need to create a new controller instance in order to apply the custom config ['url'=> 'https://twitter.com/verge/status/813055465324056576/video/1'],
$controller = new FrontController($this->container); new Config(['stream'=>true])
$result = $controller->redirect( );
$this->request->withQueryParams(['url'=>'https://twitter.com/verge/status/813055465324056576/video/1']), }
$this->response
/**
* 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() public function testRedirectWithMissingPassword()
{ {
$result = $this->controller->redirect( $this->assertRequestIsRedirect('redirect', ['url'=>'http://vimeo.com/68375962']);
$this->request->withQueryParams(['url'=>'http://vimeo.com/68375962']),
$this->response
);
$this->assertTrue($result->isRedirect());
} }
/** /**
@ -361,10 +451,6 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testRedirectWithError() public function testRedirectWithError()
{ {
$result = $this->controller->redirect( $this->assertRequestIsServerError('redirect', ['url'=>'http://example.com/foo']);
$this->request->withQueryParams(['url'=>'http://example.com/foo']),
$this->response
);
$this->assertTrue($result->isServerError());
} }
} }

View file

@ -25,7 +25,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/ */
protected function setUp() 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 * @return void
* @dataProvider urlProvider * @dataProvider urlProvider
* @dataProvider m3uUrlProvider
* @dataProvider rtmpUrlProvider
* @dataProvider remuxUrlProvider
*/ */
public function testGetURL($url, $format, $filename, $extension, $domain) public function testGetURL($url, $format, $filename, $extension, $domain)
{ {
$videoURL = $this->download->getURL($url, $format); $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() 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[] * @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() public function m3uUrlProvider()
{ {
return [ 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. * Provides incorrect URLs for tests.
* *
@ -215,8 +253,9 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
* @param string $format Format * @param string $format Format
* *
* @return void * @return void
* @dataProvider URLProvider * @dataProvider urlProvider
* @dataProvider m3uUrlProvider * @dataProvider m3uUrlProvider
* @dataProvider rtmpUrlProvider
*/ */
public function testGetJSON($url, $format) public function testGetJSON($url, $format)
{ {
@ -227,7 +266,6 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
$this->assertObjectHasAttribute('title', $info); $this->assertObjectHasAttribute('title', $info);
$this->assertObjectHasAttribute('extractor_key', $info); $this->assertObjectHasAttribute('extractor_key', $info);
$this->assertObjectHasAttribute('formats', $info); $this->assertObjectHasAttribute('formats', $info);
$this->assertObjectHasAttribute('_filename', $info);
} }
/** /**
@ -255,6 +293,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
* @return void * @return void
* @dataProvider urlProvider * @dataProvider urlProvider
* @dataProvider m3uUrlProvider * @dataProvider m3uUrlProvider
* @dataProvider rtmpUrlProvider
* @dataProvider remuxUrlProvider
*/ */
public function testGetFilename($url, $format, $filename, $extension) public function testGetFilename($url, $format, $filename, $extension)
{ {
@ -286,6 +326,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
* @return void * @return void
* @dataProvider urlProvider * @dataProvider urlProvider
* @dataProvider m3uUrlProvider * @dataProvider m3uUrlProvider
* @dataProvider rtmpUrlProvider
* @dataProvider remuxUrlProvider
*/ */
public function testGetAudioFilename($url, $format, $filename) public function testGetAudioFilename($url, $format, $filename)
{ {
@ -321,9 +363,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/ */
public function testGetAudioStreamAvconvError($url, $format) public function testGetAudioStreamAvconvError($url, $format)
{ {
$config = Config::getInstance(); $download = new VideoDownload(new Config(['avconv'=>'foobar']));
$config->avconv = 'foobar'; $download->getAudioStream($url, $format);
$this->download->getAudioStream($url, $format);
} }
/** /**
@ -334,14 +375,12 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
* *
* @return void * @return void
* @expectedException Exception * @expectedException Exception
* @dataProvider urlProvider * @dataProvider rtmpUrlProvider
*/ */
public function testGetAudioStreamCurlError($url, $format) public function testGetAudioStreamRtmpError($url, $format)
{ {
$config = Config::getInstance(); $download = new VideoDownload(new Config(['rtmpdump'=>'foobar']));
$config->curl = 'foobar'; $download->getAudioStream($url, $format);
$config->rtmpdump = 'foobar';
$this->download->getAudioStream($url, $format);
} }
/** /**
@ -359,6 +398,19 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
$this->download->getAudioStream($url, $format); $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. * Test getM3uStream function.
* *
@ -370,10 +422,46 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/ */
public function testGetM3uStream($url, $format) public function testGetM3uStream($url, $format)
{ {
$video = $this->download->getJSON($url, $format); $this->assertStream(
$stream = $this->download->getM3uStream($video); $this->download->getM3uStream(
$this->assertInternalType('resource', $stream); $this->download->getJSON($url, $format)
$this->assertFalse(feof($stream)); )
);
}
/**
* 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) public function testGetM3uStreamAvconvError($url, $format)
{ {
$config = \Alltube\Config::getInstance(); $download = new VideoDownload(new Config(['avconv'=>'foobar']));
$config->avconv = 'foobar'; $video = $download->getJSON($url, $format);
$video = $this->download->getJSON($url, $format); $download->getM3uStream($video);
$this->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);
}
}