Merge branch 'release-0.11.0'

This commit is contained in:
Pierre Rudloff 2017-12-23 19:37:37 +01:00
commit 3ae961c947
32 changed files with 1440 additions and 605 deletions

20
.appveyor.yml Normal file
View File

@ -0,0 +1,20 @@
---
install:
- sc config wuauserv start= auto
- net start wuauserv
- cinst php composer ffmpeg
- refreshenv
- copy C:\tools\php72\php.ini-development C:\tools\php72\php.ini
- echo extension=C:\tools\php72\ext\php_gmp.dll >> C:\tools\php72\php.ini
- echo extension=C:\tools\php72\ext\php_gettext.dll >> C:\tools\php72\php.ini
- echo extension=C:\tools\php72\ext\php_intl.dll >> C:\tools\php72\php.ini
- echo extension=C:\tools\php72\ext\php_openssl.dll >> C:\tools\php72\php.ini
- echo extension=C:\tools\php72\ext\php_mbstring.dll >> C:\tools\php72\php.ini
- composer install --no-dev
- composer global require phpunit/phpunit
- C:\Python36\python.exe -m pip install youtube-dl
test_script:
- phpunit
build: "off"

View File

@ -1,18 +1,18 @@
---
language: php
php: 7
php: 7.1
addons:
apt:
packages:
- language-pack-fr
install:
- composer install
script: vendor/bin/phpunit
script: composer exec -v phpunit
after_success:
- bash <(curl -s https://codecov.io/bash)
before_deploy:
- yarn install --ignore-scripts
- ./node_modules/.bin/grunt doc
- yarn grunt doc
deploy:
provider: surge
project: ./docs/

View File

@ -10,4 +10,5 @@ Before opening a new issue, make sure that:
## Translation
If you want to help translating Alltube in your language, you can join our [POEditor project](https://poeditor.com/join/project/GJmE0wN7Xw).
If you want to help translating Alltube in your language,
you can join our [POEditor project](https://poeditor.com/join/project/GJmE0wN7Xw).

View File

@ -14,6 +14,6 @@ RUN curl -sS https://getcomposer.org/installer | php
COPY resources/php.ini /usr/local/etc/php/
COPY . /var/www/html/
RUN php composer.phar install --prefer-dist
RUN yarn install
RUN yarn install --prod
RUN ./node_modules/.bin/grunt
ENV CONVERT=1

View File

@ -54,7 +54,7 @@ module.exports = function (grunt) {
},
phpunit: {
options: {
bin: 'php -dzend_extension=xdebug.so ./vendor/bin/phpunit',
bin: 'vendor/bin/phpunit',
stopOnError: true,
stopOnFailure: true,
followOutput: true
@ -111,7 +111,12 @@ module.exports = function (grunt) {
css: {
src: 'css/*'
}
}
},
markdownlint: {
doc: {
src: ['README.md', 'CONTRIBUTING.md', 'resources/*.md']
}
}
}
);
@ -128,9 +133,10 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-fixpack');
grunt.loadNpmTasks('grunt-potomo');
grunt.loadNpmTasks('grunt-contrib-csslint');
grunt.loadNpmTasks('grunt-markdownlint');
grunt.registerTask('default', ['cssmin', 'potomo']);
grunt.registerTask('lint', ['csslint', 'fixpack', 'jsonlint', 'phpcs']);
grunt.registerTask('lint', ['csslint', 'fixpack', 'jsonlint', 'markdownlint', 'phpcs']);
grunt.registerTask('test', ['phpunit']);
grunt.registerTask('doc', ['phpdocumentor']);
grunt.registerTask('release', ['default', 'githash', 'compress']);

View File

@ -14,7 +14,8 @@ You just have to unzip it on your server and it should be ready to use.
### From Git
In order to get AllTube working, you need to use [Yarn](https://yarnpkg.com/) and [Composer](https://getcomposer.org/):
In order to get AllTube working,
you need to use [Yarn](https://yarnpkg.com/) and [Composer](https://getcomposer.org/):
```bash
yarn install
@ -23,7 +24,9 @@ composer install
This will download all the required dependencies.
(Note that it will download the ffmpeg binary for 64-bits Linux. If you are on another platform, you might want to specify the path to avconv/ffmpeg in your config file.)
(Note that it will download the ffmpeg binary for 64-bits Linux.
If you are on another platform,
you might want to specify the path to avconv/ffmpeg in your config file.)
You should also ensure that the *templates_c* folder has the right permissions:
@ -31,7 +34,8 @@ You should also ensure that the *templates_c* folder has the right permissions:
chmod 777 templates_c/
```
If your web server is Apache, you need to set the `AllowOverride` setting to `All` or `FileInfo`.
If your web server is Apache,
you need to set the `AllowOverride` setting to `All` or `FileInfo`.
#### Update
@ -39,7 +43,7 @@ When updating from Git, you need to run yarn and Composer again:
```bash
git pull
yarn install
yarn install --prod
composer install
```
@ -126,16 +130,18 @@ server {
## Other dependencies
You need [avconv](https://libav.org/avconv.html) and [rtmpdump](http://rtmpdump.mplayerhq.hu/) in order to enable conversions.
You need [avconv](https://libav.org/avconv.html)
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
sudo apt-get install libav-tools
```
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).
## Use as library

View File

@ -54,13 +54,6 @@ class Config
*/
public $avconv = 'vendor/bin/ffmpeg';
/**
* rtmpdump binary path.
*
* @var string
*/
public $rtmpdump = 'vendor/bin/rtmpdump';
/**
* Disable URL rewriting.
*
@ -82,6 +75,21 @@ class Config
*/
public $remux = false;
/**
* MP3 bitrate when converting (in kbit/s).
*
* @var int
*/
public $audioBitrate = 128;
/**
* avconv/ffmpeg logging level.
* Must be one of these: quiet, panic, fatal, error, warning, info, verbose, debug.
*
* @var string
*/
public $avconvVerbosity = 'error';
/**
* YAML config file path.
*
@ -96,7 +104,6 @@ class Config
* * youtubedl: youtube-dl binary path
* * python: Python binary path
* * avconv: avconv or ffmpeg binary path
* * rtmpdump: rtmpdump binary path
* * params: Array of youtube-dl parameters
* * convert: Enable conversion?
*

View File

@ -91,7 +91,7 @@ class Locale
/**
* Get country information from locale.
*
* @return \Rinvex\Country\Country
* @return \Rinvex\Country\Country|array
*/
public function getCountry()
{

View File

@ -5,7 +5,7 @@
namespace Alltube;
use Symfony\Component\Process\ProcessBuilder;
use Symfony\Component\Process\Process;
/**
* Class used to manage locales.
@ -42,7 +42,6 @@ class LocaleManager
{
$session_factory = new \Aura\Session\SessionFactory();
$session = $session_factory->newInstance($cookies);
$session->setCookieParams(['httponly' => true]);
$this->sessionSegment = $session->getSegment('Alltube\LocaleManager');
$cookieLocale = $this->sessionSegment->get('locale');
if (isset($cookieLocale)) {
@ -58,8 +57,7 @@ class LocaleManager
public function getSupportedLocales()
{
$return = [];
$builder = new ProcessBuilder(['locale', '-a']);
$process = $builder->getProcess();
$process = new Process(['locale', '-a']);
$process->run();
$installedLocales = explode(PHP_EOL, trim($process->getOutput()));
foreach ($this->supportedLocales as $supportedLocale) {
@ -95,4 +93,13 @@ class LocaleManager
$this->curLocale = $locale;
$this->sessionSegment->set('locale', $locale);
}
/**
* Unset the current locale.
*/
public function unsetLocale()
{
$this->curLocale = null;
$this->sessionSegment->clear();
}
}

View File

@ -61,10 +61,10 @@ class PlaylistArchiveStream extends TarArchive
/**
* PlaylistArchiveStream constructor.
*/
public function __construct()
public function __construct(Config $config = null)
{
$this->client = new \GuzzleHttp\Client();
$this->download = new VideoDownload();
$this->download = new VideoDownload($config);
}
/**
@ -78,7 +78,9 @@ class PlaylistArchiveStream extends TarArchive
{
$pos = ftell($this->buffer);
fwrite($this->buffer, $data);
fseek($this->buffer, $pos);
if ($pos !== false) {
fseek($this->buffer, $pos);
}
}
/**
@ -91,7 +93,10 @@ class PlaylistArchiveStream extends TarArchive
public function stream_open($path)
{
$this->format = ltrim(parse_url($path, PHP_URL_PATH), '/');
$this->buffer = fopen('php://temp', 'r+');
$buffer = fopen('php://temp', 'r+');
if ($buffer !== false) {
$this->buffer = $buffer;
}
foreach (explode(';', parse_url($path, PHP_URL_HOST)) as $url) {
$this->files[] = [
'url' => urldecode($url),
@ -131,7 +136,7 @@ class PlaylistArchiveStream extends TarArchive
/**
* Called when ftell() is used on the stream.
*
* @return int
* @return int|false
*/
public function stream_tell()
{
@ -171,7 +176,7 @@ class PlaylistArchiveStream extends TarArchive
*
* @param int $count Number of bytes to read
*
* @return string
* @return string|false
*/
public function stream_read($count)
{

View File

@ -5,8 +5,7 @@
namespace Alltube;
use Chain\Chain;
use Symfony\Component\Process\ProcessBuilder;
use Symfony\Component\Process\Process;
/**
* Extract info about videos.
@ -20,15 +19,11 @@ class VideoDownload
*/
private $config;
/**
* ProcessBuilder instance used to call Python.
*
* @var ProcessBuilder
*/
private $procBuilder;
/**
* VideoDownload constructor.
*
* @throws \Exception If youtube-dl is missing
* @throws \Exception If Python is missing
*/
public function __construct(Config $config = null)
{
@ -37,16 +32,27 @@ class VideoDownload
} else {
$this->config = Config::getInstance();
}
$this->procBuilder = new ProcessBuilder();
if (!is_file($this->config->youtubedl)) {
throw new \Exception("Can't find youtube-dl at ".$this->config->youtubedl);
} elseif (!is_file($this->config->python)) {
} elseif (!$this->checkCommand([$this->config->python, '--version'])) {
throw new \Exception("Can't find Python at ".$this->config->python);
}
$this->procBuilder->setPrefix(
}
/**
* Return a youtube-dl process with the specified arguments.
*
* @param string[] $arguments Arguments
*
* @return Process
*/
private function getProcess(array $arguments)
{
return new Process(
array_merge(
[$this->config->python, $this->config->youtubedl],
$this->config->params
$this->config->params,
$arguments
)
);
}
@ -58,7 +64,7 @@ class VideoDownload
* */
public function listExtractors()
{
return explode(PHP_EOL, trim($this->getProp(null, null, 'list-extractors')));
return explode("\n", trim($this->getProp(null, null, 'list-extractors')));
}
/**
@ -69,24 +75,30 @@ class VideoDownload
* @param string $prop Property
* @param string $password Video password
*
* @throws PasswordException If the video is protected by a password and no password was specified
* @throws \Exception If the password is wrong
* @throws \Exception If youtube-dl returns an error
*
* @return string
*/
private function getProp($url, $format = null, $prop = 'dump-json', $password = null)
{
$this->procBuilder->setArguments(
[
'--'.$prop,
$url,
]
);
$arguments = [
'--'.$prop,
$url,
];
if (isset($format)) {
$this->procBuilder->add('-f '.$format);
$arguments[] = '-f '.$format;
}
if (isset($password)) {
$this->procBuilder->add('--video-password');
$this->procBuilder->add($password);
$arguments[] = '--video-password';
$arguments[] = $password;
}
$process = $this->procBuilder->getProcess();
$process = $this->getProcess($arguments);
//This is needed by the openload extractor because it runs PhantomJS
$process->setEnv(['QT_QPA_PLATFORM'=>'offscreen']);
$process->inheritEnvironmentVariables();
$process->run();
if (!$process->isSuccessful()) {
$errorOutput = trim($process->getErrorOutput());
@ -131,7 +143,7 @@ class VideoDownload
* */
public function getURL($url, $format = null, $password = null)
{
return explode(PHP_EOL, $this->getProp($url, $format, 'get-url', $password));
return explode("\n", $this->getProp($url, $format, 'get-url', $password));
}
/**
@ -185,59 +197,38 @@ class VideoDownload
}
/**
* Add options to a process builder running rtmp.
*
* @param ProcessBuilder $builder Process builder
* @param object $video Video object returned by youtube-dl
*
* @return ProcessBuilder
*/
private function addOptionsToRtmpProcess(ProcessBuilder $builder, $video)
{
foreach ([
'url' => 'rtmp',
'webpage_url' => 'pageUrl',
'player_url' => 'swfVfy',
'flash_version' => 'flashVer',
'play_path' => 'playpath',
'app' => 'app',
] as $property => $option) {
if (isset($video->{$property})) {
$builder->add('--'.$option);
$builder->add($video->{$property});
}
}
return $builder;
}
/**
* Get a process that runs rtmp in order to download a video.
* Return arguments used to run rtmp for a specific video.
*
* @param object $video Video object returned by youtube-dl
*
* @return \Symfony\Component\Process\Process Process
* @return array Arguments
*/
private function getRtmpProcess(\stdClass $video)
private function getRtmpArguments(\stdClass $video)
{
if (!$this->checkCommand([$this->config->rtmpdump, '--help'])) {
throw(new \Exception('Can\'t find rtmpdump'));
}
$builder = new ProcessBuilder(
[
$this->config->rtmpdump,
'-q',
]
);
$builder = $this->addOptionsToRtmpProcess($builder, $video);
if (isset($video->rtmp_conn)) {
foreach ($video->rtmp_conn as $conn) {
$builder->add('--conn');
$builder->add($conn);
$arguments = [];
foreach ([
'url' => '-rtmp_tcurl',
'webpage_url' => '-rtmp_pageurl',
'player_url' => '-rtmp_swfverify',
'flash_version' => '-rtmp_flashver',
'play_path' => '-rtmp_playpath',
'app' => '-rtmp_app',
] as $property => $option) {
if (isset($video->{$property})) {
$arguments[] = $option;
$arguments[] = $video->{$property};
}
}
return $builder->getProcess();
if (isset($video->rtmp_conn)) {
foreach ($video->rtmp_conn as $conn) {
$arguments[] = '-rtmp_conn';
$arguments[] = $conn;
}
}
return $arguments;
}
/**
@ -249,8 +240,7 @@ class VideoDownload
*/
private function checkCommand(array $command)
{
$builder = ProcessBuilder::create($command);
$process = $builder->getProcess();
$process = new Process($command);
$process->run();
return $process->isSuccessful();
@ -259,30 +249,45 @@ class VideoDownload
/**
* Get a process that runs avconv in order to convert a video to MP3.
*
* @param string $url URL of the video file
* @param object $url Video object returned by youtube-dl
*
* @throws \Exception If avconv/ffmpeg is missing
*
* @return \Symfony\Component\Process\Process Process
*/
private function getAvconvMp3Process($url)
private function getAvconvMp3Process(\stdClass $video)
{
if (!$this->checkCommand([$this->config->avconv, '-version'])) {
throw(new \Exception('Can\'t find avconv or ffmpeg'));
}
$builder = ProcessBuilder::create(
if ($video->protocol == 'rtmp') {
$rtmpArguments = $this->getRtmpArguments($video);
} else {
$rtmpArguments = [];
}
$arguments = array_merge(
[
$this->config->avconv,
'-v', 'quiet',
//Vimeo needs a correct user-agent
'-user-agent', $this->getProp(null, null, 'dump-user-agent'),
'-i', $url,
'-v', $this->config->avconvVerbosity,
],
$rtmpArguments,
[
'-i', $video->url,
'-f', 'mp3',
'-b:a', $this->config->audioBitrate.'k',
'-vn',
'pipe:1',
]
);
if ($video->url != '-') {
//Vimeo needs a correct user-agent
$arguments[] = '-user_agent';
$arguments[] = $this->getProp(null, null, 'dump-user-agent');
}
return $builder->getProcess();
return new Process($arguments);
}
/**
@ -292,6 +297,9 @@ class VideoDownload
* @param string $format Format to use for the video
* @param string $password Video password
*
* @throws \Exception If your try to convert and M3U8 video
* @throws \Exception If the popen stream was not created correctly
*
* @return resource popen stream
*/
public function getAudioStream($url, $format, $password = null)
@ -301,17 +309,15 @@ class VideoDownload
throw(new \Exception('Conversion of M3U8 files is not supported.'));
}
if (parse_url($video->url, PHP_URL_SCHEME) == 'rtmp') {
$process = $this->getRtmpProcess($video);
$chain = new Chain($process);
$chain->add('|', $this->getAvconvMp3Process('-'));
$avconvProc = $this->getAvconvMp3Process($video);
return popen($chain->getProcess()->getCommandLine(), 'r');
} else {
$avconvProc = $this->getAvconvMp3Process($video->url);
$stream = popen($avconvProc->getCommandLine(), 'r');
return popen($avconvProc->getCommandLine(), 'r');
if (!is_resource($stream)) {
throw new \Exception('Could not open popen stream.');
}
return $stream;
}
/**
@ -319,6 +325,9 @@ class VideoDownload
*
* @param \stdClass $video Video object returned by getJSON
*
* @throws \Exception If avconv/ffmpeg is missing
* @throws \Exception If the popen stream was not created correctly
*
* @return resource popen stream
*/
public function getM3uStream(\stdClass $video)
@ -327,10 +336,10 @@ class VideoDownload
throw(new \Exception('Can\'t find avconv or ffmpeg'));
}
$procBuilder = ProcessBuilder::create(
$process = new Process(
[
$this->config->avconv,
'-v', 'quiet',
'-v', $this->config->avconvVerbosity,
'-i', $video->url,
'-f', $video->ext,
'-c', 'copy',
@ -340,7 +349,12 @@ class VideoDownload
]
);
return popen($procBuilder->getProcess()->getCommandLine(), 'r');
$stream = popen($process->getCommandLine(), 'r');
if (!is_resource($stream)) {
throw new \Exception('Could not open popen stream.');
}
return $stream;
}
/**
@ -348,14 +362,16 @@ class VideoDownload
*
* @param array $urls URLs of the video ($urls[0]) and audio ($urls[1]) files
*
* @throws \Exception If the popen stream was not created correctly
*
* @return resource popen stream
*/
public function getRemuxStream(array $urls)
{
$procBuilder = ProcessBuilder::create(
$process = new Process(
[
$this->config->avconv,
'-v', 'quiet',
'-v', $this->config->avconvVerbosity,
'-i', $urls[0],
'-i', $urls[1],
'-c', 'copy',
@ -366,7 +382,12 @@ class VideoDownload
]
);
return popen($procBuilder->getProcess()->getCommandLine(), 'r');
$stream = popen($process->getCommandLine(), 'r');
if (!is_resource($stream)) {
throw new \Exception('Could not open popen stream.');
}
return $stream;
}
/**
@ -374,11 +395,32 @@ class VideoDownload
*
* @param \stdClass $video Video object returned by getJSON
*
* @throws \Exception If the popen stream was not created correctly
*
* @return resource popen stream
*/
public function getRtmpStream(\stdClass $video)
{
return popen($this->getRtmpProcess($video)->getCommandLine(), 'r');
$process = new Process(
array_merge(
[
$this->config->avconv,
'-v', $this->config->avconvVerbosity,
],
$this->getRtmpArguments($video),
[
'-i', $video->url,
'-f', $video->ext,
'pipe:1',
]
)
);
$stream = popen($process->getCommandLine(), 'r');
if (!is_resource($stream)) {
throw new \Exception('Could not open popen stream.');
}
return $stream;
}
/**
@ -387,7 +429,9 @@ class VideoDownload
* @param object $video Video object returned by youtube-dl
* @param string $format Requested format
*
* @return Response HTTP response
* @throws \Exception If the popen stream was not created correctly
*
* @return resource
*/
public function getPlaylistArchiveStream(\stdClass $video, $format)
{
@ -396,6 +440,9 @@ class VideoDownload
$playlistItems[] = urlencode($entry->url);
}
$stream = fopen('playlist://'.implode(';', $playlistItems).'/'.$format, 'r');
if (!is_resource($stream)) {
throw new \Exception('Could not fopen popen stream.');
}
return $stream;
}

View File

@ -18,8 +18,8 @@ class ViewFactory
/**
* Create Smarty view object.
*
* @param Container $container Slim dependency container
* @param Request $request PSR-7 request
* @param ContainerInterface $container Slim dependency container
* @param Request $request PSR-7 request
*
* @return Smarty
*/

View File

@ -5,26 +5,25 @@
"homepage": "http://alltubedownload.net/",
"type": "project",
"require": {
"smarty/smarty": "~3.1.29",
"slim/slim": "~3.8.1",
"slim/slim": "~3.9.2",
"mathmarques/smarty-view": "~1.1.0",
"symfony/yaml": "~3.3.9",
"symfony/process": "~3.3.9",
"ptachoire/process-builder-chain": "~1.2.0",
"symfony/yaml": "~3.4.1",
"symfony/process": "~3.4.1",
"guzzlehttp/guzzle": "~6.3.0",
"aura/session": "~2.1.0",
"barracudanetworks/archivestream-php": "~1.0.5",
"smarty-gettext/smarty-gettext": "~1.5.1",
"zonuexe/http-accept-language": "~0.4.1",
"rinvex/country": "~2.0.0"
"rinvex/country": "~3.1.0",
"php-mock/php-mock-mockery": "~1.2.0"
},
"require-dev": {
"symfony/var-dumper": "~3.3.9",
"squizlabs/php_codesniffer": "~3.1.0",
"phpunit/phpunit": "~5.7.2",
"ffmpeg/ffmpeg": "dev-release",
"rg3/youtube-dl": "~2017.10.20",
"rudloff/rtmpdump-bin": "~2.3.0",
"symfony/var-dumper": "~3.4.1",
"squizlabs/php_codesniffer": "~3.2.2",
"phpunit/phpunit": "~6.5.2",
"doctrine/instantiator": "~1.0.0",
"ffmpeg/ffmpeg": "3.4.1",
"rg3/youtube-dl": "2017.12.10",
"heroku/heroku-buildpack-php": "*"
},
"extra": {
@ -39,10 +38,10 @@
"type": "package",
"package": {
"name": "rg3/youtube-dl",
"version": "2017.10.20",
"version": "2017.12.10",
"dist": {
"type": "zip",
"url": "https://github.com/rg3/youtube-dl/archive/2017.10.20.zip"
"url": "https://github.com/rg3/youtube-dl/archive/2017.12.10.zip"
}
}
},
@ -50,9 +49,9 @@
"type": "package",
"package": {
"name": "ffmpeg/ffmpeg",
"version": "dev-release",
"version": "3.4.1",
"dist": {
"url": "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-64bit-static.tar.xz",
"url": "https://johnvansickle.com/ffmpeg/releases/ffmpeg-3.4.1-64bit-static.tar.xz",
"type": "xz"
},
"bin": [
@ -78,11 +77,13 @@
"autoload": {
"psr-4": {
"Alltube\\": "classes/",
"Alltube\\Controller\\": "controllers/"
"Alltube\\Controller\\": "controllers/",
"Alltube\\Test\\": "tests/"
}
},
"scripts": {
"compile": "composer install --ignore-platform-reqs",
"update-locales": "tsmarty2c.php templates > i18n/template.pot"
"update-locales": "tsmarty2c.php templates > i18n/template.pot",
"youtube-dl": "vendor/rg3/youtube-dl/youtube_dl/__main__.py"
}
}

1010
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,10 +7,10 @@ python: /usr/bin/python
# An array of parameters to pass to youtube-dl
params:
- --no-playlist
- --no-warnings
- --playlist-end
- 1
- --ignore-errors
- --flat-playlist
- --restrict-filenames
# True to enable audio conversion
convert: false
@ -18,11 +18,14 @@ convert: false
# Path to your avconv or ffmpeg binary
avconv: vendor/bin/ffmpeg
# Path to your rtmpdump binary
rtmpdump: vendor/bin/rtmpdump
# avconv/ffmpeg logging level.
avconvVerbosity: error
# True to disable URL rewriting
uglyUrls: false
# True to stream videos through server
stream: false
# MP3 bitrate when converting (in kbit/s)
audioBitrate: 128

View File

@ -1,2 +1,3 @@
---
convert: false
avconvVerbosity: fatal

View File

@ -0,0 +1,12 @@
---
convert: false
python: C:\Python36\python.exe
avconv: C:\ProgramData\chocolatey\bin\ffmpeg.exe
avconvVerbosity: fatal
youtubedl: C:\Python36\Lib\site-packages\youtube_dl\__main__.py
params:
- --no-warnings
- --ignore-errors
- --flat-playlist
- --restrict-filenames
- --no-check-certificate

View File

@ -7,6 +7,7 @@ namespace Alltube\Controller;
use Alltube\Config;
use Alltube\Locale;
use Alltube\LocaleManager;
use Alltube\PasswordException;
use Alltube\VideoDownload;
use Psr\Container\ContainerInterface;
@ -83,7 +84,7 @@ class FrontController
} else {
$this->config = Config::getInstance();
}
$this->download = new VideoDownload();
$this->download = new VideoDownload($this->config);
$this->container = $container;
$this->view = $this->container->get('view');
$this->localeManager = $this->container->get('locale');
@ -360,7 +361,7 @@ class FrontController
$stream = $this->download->getRtmpStream($video);
$response = $response->withHeader('Content-Type', 'video/'.$video->ext);
$body = new Stream($stream);
} elseif ($video->protocol == 'm3u8') {
} elseif ($video->protocol == 'm3u8' || $video->protocol == 'm3u8_native') {
$stream = $this->download->getM3uStream($video);
$response = $response->withHeader('Content-Type', 'video/'.$video->ext);
$body = new Stream($stream);

View File

@ -1,30 +1,32 @@
{
"name": "alltube",
"description": "HTML GUI for youtube-dl",
"version": "0.10.2",
"version": "0.11.0",
"author": "Pierre Rudloff",
"bugs": "https://github.com/Rudloff/alltube/issues",
"dependencies": {
"grunt": "~1.0.1",
"grunt-contrib-csslint": "~2.0.0",
"grunt-contrib-cssmin": "~2.2.1",
"grunt-contrib-uglify": "~3.1.0",
"grunt-contrib-uglify": "~3.2.1",
"grunt-potomo": "~3.5.0",
"open-sans-fontface": "~1.4.0"
},
"devDependencies": {
"grunt-contrib-compress": "~1.4.1",
"grunt-contrib-csslint": "~2.0.0",
"grunt-contrib-watch": "~1.0.0",
"grunt-fixpack": "~0.1.0",
"grunt-githash": "~0.1.3",
"grunt-jslint": "~1.1.15",
"grunt-jsonlint": "~1.1.0",
"grunt-markdownlint": "~1.0.43",
"grunt-phpcs": "~0.4.0",
"grunt-phpdocumentor": "~0.4.1",
"grunt-phpunit": "~0.3.6"
},
"engines": {
"yarn": ">= 1.0.0"
"npm": "~3",
"yarn": "~1"
},
"homepage": "https://www.alltubedownload.net/",
"keywords": [
@ -34,7 +36,7 @@
"youtube"
],
"license": "GPL-3.0",
"main": "index.php",
"main": "Gruntfile.js",
"repository": {
"type": "git",
"url": "https://github.com/Rudloff/alltube.git"

View File

@ -4,7 +4,8 @@
## My browser plays the video. How do I download it?
Most recent browsers automatically play a video if it is a format they know how to play.
Most recent browsers automatically play a video
if it is a format they know how to play.
You can ususally download the video by doing *File > Save to* or *ctrl + S*.
## How do I change config parameters?
@ -17,7 +18,6 @@ Here are the parameters that you can set:
* `params`: an array of parameters to pass to youtube-dl
* `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/config.example.yml) for default values.
@ -56,13 +56,16 @@ Then push the code to Heroku and it should work out of the box.
## Why can't I download videos from some websites (e.g. Dailymotion)
Some websites generate an unique video URL for each IP address. When using Alltube, the URL is generated for our server's IP address and your computer is not allowed to use it.
Some websites generate an unique video URL for each IP address.
When using Alltube, the URL is generated for our server's IP address
and your computer is not allowed to use it.
There are two known workarounds:
* You can run Alltube locally on your computer.
* You can enable streaming videos through the server (see below).
Please note that this can use a lot of resources on the server (which is why we won't enable it on alltubedownload.net).
Please note that this can use a lot of resources on the server
(which is why we won't enable it on alltubedownload.net).
## CSS and JavaScript files are missing
@ -74,7 +77,8 @@ You need to either:
## I get a 404 error on every page except the index
This is probably because your server does not have mod_rewrite or AllowOverride is disabled.
This is probably because your server does not have mod_rewrite
or AllowOverride is disabled.
You can work around this by adding this to your `config.yml` file:
```yaml
@ -101,7 +105,8 @@ Alltube can rename videos automatically if you enable streaming (see above).
## I want to download a video that isn't available in my country
If the video is available in the server's country, you can download it if you enable streaming (see above).
If the video is available in the server's country,
you can download it if you enable streaming (see above).
## How do I run the Docker image?
@ -114,7 +119,9 @@ docker run -p 8080:80 rudloff/alltube
You should be able to use `heroku local` like this:
```bash
sudo APACHE_LOCK_DIR=. APACHE_PID_FILE=./pid APACHE_RUN_USER=www-data APACHE_RUN_GROUP=www-data APACHE_LOG_DIR=. heroku local
sudo APACHE_LOCK_DIR=. APACHE_PID_FILE=./pid APACHE_RUN_USER=www-data \
APACHE_RUN_GROUP=www-data APACHE_LOG_DIR=. \
heroku local
```
You might need to create some symlinks before that:
@ -124,7 +131,8 @@ ln -s /usr/sbin/apache2 /usr/sbin/httpd
ln -s /usr/sbin/php-fpm7.0 /usr/sbin/php-fpm
```
And you probably need to run this in another terminal after `heroku local` has finished launching `php-fpm`:
And you probably need to run this in another terminal
after `heroku local` has finished launching `php-fpm`:
```bash
chmod 0667 /tmp/heroku.fcgi.5000.sock
@ -137,4 +145,5 @@ 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.
You can also enable the experimental remux mode
that will merge the best video and the best audio format on the fly.

View File

@ -6,11 +6,12 @@
namespace Alltube\Test;
use Alltube\Config;
use PHPUnit\Framework\TestCase;
/**
* Unit tests for the Config class.
*/
class ConfigTest extends \PHPUnit_Framework_TestCase
class ConfigTest extends TestCase
{
/**
* Config class instance.
@ -45,11 +46,27 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
public function testGetInstance()
{
$this->assertEquals($this->config->convert, false);
$this->assertInternalType('array', $this->config->params);
$this->assertInternalType('string', $this->config->youtubedl);
$this->assertInternalType('string', $this->config->python);
$this->assertInternalType('string', $this->config->avconv);
$this->assertInternalType('string', $this->config->rtmpdump);
$this->assertConfig($this->config);
}
/**
* Assert that a Config object is correctly instantiated.
*
* @param Config $config Config class instance.
*
* @return void
*/
private function assertConfig(Config $config)
{
$this->assertInternalType('array', $config->params);
$this->assertInternalType('string', $config->youtubedl);
$this->assertInternalType('string', $config->python);
$this->assertInternalType('string', $config->avconv);
$this->assertInternalType('bool', $config->convert);
$this->assertInternalType('bool', $config->uglyUrls);
$this->assertInternalType('bool', $config->stream);
$this->assertInternalType('bool', $config->remux);
$this->assertInternalType('int', $config->audioBitrate);
}
/**
@ -64,13 +81,14 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
}
/**
* Test the getInstance function with aen empty filename.
* Test the getInstance function with an empty filename.
*
* @return void
*/
public function testGetInstanceWithEmptyFile()
{
Config::getInstance('');
$config = Config::getInstance('');
$this->assertConfig($config);
}
/**

View File

@ -9,6 +9,7 @@ use Alltube\Config;
use Alltube\Controller\FrontController;
use Alltube\LocaleManager;
use Alltube\ViewFactory;
use PHPUnit\Framework\TestCase;
use Slim\Container;
use Slim\Http\Environment;
use Slim\Http\Request;
@ -17,7 +18,7 @@ use Slim\Http\Response;
/**
* Unit tests for the FrontController class.
*/
class FrontControllerTest extends \PHPUnit_Framework_TestCase
class FrontControllerTest extends TestCase
{
/**
* Slim dependency container.
@ -47,6 +48,13 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
private $controller;
/**
* Config class instance.
*
* @var Config
*/
private $config;
/**
* Prepare tests.
*/
@ -57,7 +65,15 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
$this->response = new Response();
$this->container['view'] = ViewFactory::create($this->container, $this->request);
$this->container['locale'] = new LocaleManager();
$this->controller = new FrontController($this->container, Config::getInstance('config/config_test.yml'));
if (PHP_OS == 'WINNT') {
$configFile = 'config_test_windows.yml';
} else {
$configFile = 'config_test.yml';
}
$this->config = Config::getInstance('config/'.$configFile);
$this->controller = new FrontController($this->container, $this->config);
$this->container['router']->map(['GET'], '/', [$this->controller, 'index'])
->setName('index');
$this->container['router']->map(['GET'], '/video', [$this->controller, 'video'])
@ -149,6 +165,18 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
* @return void
*/
public function testConstructor()
{
$controller = new FrontController($this->container, $this->config);
$this->assertInstanceOf(FrontController::class, $controller);
}
/**
* Test the constructor with a default config.
*
* @return void
* @requires OS Linux
*/
public function testConstructorWithDefaultConfig()
{
$controller = new FrontController($this->container);
$this->assertInstanceOf(FrontController::class, $controller);
@ -161,7 +189,8 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testConstructorWithStream()
{
$controller = new FrontController($this->container, new Config(['stream' => true]));
$this->config->stream = true;
$controller = new FrontController($this->container, $this->config);
$this->assertInstanceOf(FrontController::class, $controller);
}
@ -231,17 +260,6 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
$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->markTestSkipped('This URL triggers a curl SSL error on Travis');
$this->assertRequestIsOk('video', ['url' => 'http://html5demos.com/video']);
}
/**
* Test the video() function with audio conversion.
*
@ -261,7 +279,10 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
{
$this->assertRequestIsRedirect(
'video',
['url' => 'https://2080.bandcamp.com/track/cygnus-x-the-orange-theme-2080-faulty-chip-cover', 'audio' => true]
[
'url' => 'https://2080.bandcamp.com/track/cygnus-x-the-orange-theme-2080-faulty-chip-cover',
'audio' => true,
]
);
}
@ -298,12 +319,12 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testVideoWithStream()
{
$config = new Config(['stream' => true]);
$this->assertRequestIsOk('video', ['url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU'], $config);
$this->config->stream = true;
$this->assertRequestIsOk('video', ['url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU'], $this->config);
$this->assertRequestIsOk(
'video',
['url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'audio' => true],
$config
$this->config
);
}
@ -371,10 +392,11 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testRedirectWithStream()
{
$this->config->stream = true;
$this->assertRequestIsOk(
'redirect',
['url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU'],
new Config(['stream' => true])
$this->config
);
}
@ -385,10 +407,14 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testRedirectWithM3uStream()
{
$this->config->stream = true;
$this->assertRequestIsOk(
'redirect',
['url' => 'https://twitter.com/verge/status/813055465324056576/video/1'],
new Config(['stream' => true])
[
'url' => 'https://twitter.com/verge/status/813055465324056576/video/1',
'format' => 'hls-2176',
],
$this->config
);
}
@ -399,10 +425,11 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testRedirectWithRtmpStream()
{
$this->config->stream = true;
$this->assertRequestIsOk(
'redirect',
['url' => 'http://www.canalc2.tv/video/12163', 'format' => 'rtmp'],
new Config(['stream' => true])
$this->config
);
}
@ -413,13 +440,14 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
*/
public function testRedirectWithRemux()
{
$this->config->remux = true;
$this->assertRequestIsOk(
'redirect',
[
'url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU',
'format' => 'bestvideo+bestaudio',
],
new Config(['remux' => true])
$this->config
);
}
@ -477,13 +505,15 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
* Test the redirect() function with a playlist stream.
*
* @return void
* @requires OS Linux
*/
public function testRedirectWithPlaylist()
{
$this->config->stream = true;
$this->assertRequestIsOk(
'redirect',
['url' => 'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC'],
new Config(['stream' => true])
$this->config
);
}

View File

@ -7,11 +7,12 @@ namespace Alltube\Test;
use Alltube\Locale;
use Alltube\LocaleManager;
use PHPUnit\Framework\TestCase;
/**
* Unit tests for the Config class.
*/
class LocaleManagerTest extends \PHPUnit_Framework_TestCase
class LocaleManagerTest extends TestCase
{
/**
* LocaleManager class instance.
@ -26,6 +27,7 @@ class LocaleManagerTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
$this->localeManager = new LocaleManager();
$_SESSION['Alltube\LocaleManager']['locale'] = 'foo_BAR';
}
/**
@ -35,7 +37,6 @@ class LocaleManagerTest extends \PHPUnit_Framework_TestCase
*/
public function testConstructorWithCookies()
{
$_SESSION['Alltube\LocaleManager']['locale'] = 'foo_BAR';
$localeManager = new LocaleManager([]);
$this->assertEquals('foo_BAR', (string) $localeManager->getLocale());
}
@ -59,7 +60,7 @@ class LocaleManagerTest extends \PHPUnit_Framework_TestCase
*/
public function testGetLocale()
{
$this->assertNull($this->localeManager->getLocale());
$this->assertEquals(new Locale('foo_BAR'), $this->localeManager->getLocale());
}
/**
@ -74,4 +75,26 @@ class LocaleManagerTest extends \PHPUnit_Framework_TestCase
$this->assertInstanceOf(Locale::class, $locale);
$this->assertEquals('foo_BAR', (string) $locale);
}
/**
* Test the unsetLocale function.
*
* @return void
*/
public function testUnsetLocale()
{
$this->localeManager->unsetLocale();
$this->assertNull($this->localeManager->getLocale());
}
/**
* Test that the environment is correctly set up.
*
* @return void
*/
public function testEnv()
{
$this->localeManager->setLocale(new Locale('foo_BAR'));
$this->assertEquals('foo_BAR', getenv('LANG'));
}
}

View File

@ -5,8 +5,10 @@
namespace Alltube\Test;
use Alltube\Locale;
use Alltube\LocaleManager;
use Alltube\LocaleMiddleware;
use PHPUnit\Framework\TestCase;
use Slim\Container;
use Slim\Http\Environment;
use Slim\Http\Request;
@ -15,7 +17,7 @@ use Slim\Http\Response;
/**
* Unit tests for the FrontController class.
*/
class LocaleMiddlewareTest extends \PHPUnit_Framework_TestCase
class LocaleMiddlewareTest extends TestCase
{
/**
* LocaleMiddleware instance.
@ -24,29 +26,46 @@ class LocaleMiddlewareTest extends \PHPUnit_Framework_TestCase
*/
private $middleware;
/**
* Slim dependency container.
*
* @var Container
*/
private $container;
/**
* Prepare tests.
*/
protected function setUp()
{
$container = new Container();
$container['locale'] = new LocaleManager();
$this->middleware = new LocaleMiddleware($container);
$this->container = new Container();
$this->container['locale'] = new LocaleManager();
$this->middleware = new LocaleMiddleware($this->container);
}
/**
* Unset locale cookie after each test.
*
* @return void
*/
protected function tearDown()
{
$this->container['locale']->unsetLocale();
}
/**
* Test the testLocale() function.
*
* @return void
* @requires OS Linux
*/
public function testTestLocale()
{
$this->markTestSkipped('For some reason, this test fails on Travis even if the fr_FR locale is installed.');
$locale = [
'language' => 'fr',
'region' => 'FR',
'language' => 'en',
'region' => 'US',
];
$this->assertEquals('fr_FR', $this->middleware->testLocale($locale));
$this->assertEquals('en_US', $this->middleware->testLocale($locale));
}
/**
@ -65,12 +84,29 @@ class LocaleMiddlewareTest extends \PHPUnit_Framework_TestCase
}
/**
* Mock function that does nothing.
* Check that the request contains an Accept-Language header.
*
* @param Request $request PSR-7 request
*
* @return void
*/
public function nothing()
public function assertHeader(Request $request)
{
$header = $request->getHeader('Accept-Language');
$this->assertEquals('foo-BAR', $header[0]);
}
/**
* Check that the request contains no Accept-Language header.
*
* @param Request $request PSR-7 request
*
* @return void
*/
public function assertNoHeader(Request $request)
{
$header = $request->getHeader('Accept-Language');
$this->assertEmpty($header);
}
/**
@ -82,14 +118,14 @@ class LocaleMiddlewareTest extends \PHPUnit_Framework_TestCase
{
$request = Request::createFromEnvironment(Environment::mock());
$this->middleware->__invoke(
$request->withHeader('Accept-Language', 'fr-FR'),
$request->withHeader('Accept-Language', 'foo-BAR'),
new Response(),
[$this, 'nothing']
[$this, 'assertHeader']
);
}
/**
* Test the __invoke() function withot the Accept-Language header.
* Test the __invoke() function without the Accept-Language header.
*
* @return void
*/
@ -99,17 +135,7 @@ class LocaleMiddlewareTest extends \PHPUnit_Framework_TestCase
$this->middleware->__invoke(
$request->withoutHeader('Accept-Language'),
new Response(),
[$this, 'nothing']
[$this, 'assertNoHeader']
);
}
/**
* Test that the environment is correctly set up.
*
* @return void
*/
public function testEnv()
{
$this->markTestIncomplete('We need to find a way to reliably test LC_ALL and LANG values');
}
}

View File

@ -6,25 +6,26 @@
namespace Alltube\Test;
use Alltube\Locale;
use PHPUnit\Framework\TestCase;
/**
* Unit tests for the Config class.
*/
class LocaleTest extends \PHPUnit_Framework_TestCase
class LocaleTest extends TestCase
{
/**
* Locale class instance.
*
* @var Locale
*/
private $locale;
private $localeObject;
/**
* Prepare tests.
*/
protected function setUp()
{
$this->locale = new Locale('fr_FR');
$this->localeObject = new Locale('fr_FR');
}
/**
@ -34,7 +35,7 @@ class LocaleTest extends \PHPUnit_Framework_TestCase
*/
public function testGetToString()
{
$this->assertEquals('fr_FR', $this->locale->__toString());
$this->assertEquals('fr_FR', $this->localeObject->__toString());
}
/**
@ -44,7 +45,7 @@ class LocaleTest extends \PHPUnit_Framework_TestCase
*/
public function testGetFullName()
{
$this->assertEquals('français (France)', $this->locale->getFullName());
$this->assertEquals('français (France)', $this->localeObject->getFullName());
}
/**
@ -54,7 +55,7 @@ class LocaleTest extends \PHPUnit_Framework_TestCase
*/
public function testGetIso15897()
{
$this->assertEquals('fr_FR', $this->locale->getIso15897());
$this->assertEquals('fr_FR', $this->localeObject->getIso15897());
}
/**
@ -64,7 +65,7 @@ class LocaleTest extends \PHPUnit_Framework_TestCase
*/
public function testGetBcp47()
{
$this->assertEquals('fr-FR', $this->locale->getBcp47());
$this->assertEquals('fr-FR', $this->localeObject->getBcp47());
}
/**
@ -74,6 +75,6 @@ class LocaleTest extends \PHPUnit_Framework_TestCase
*/
public function testGetIso3166()
{
$this->assertEquals('fr', $this->locale->getIso3166());
$this->assertEquals('fr', $this->localeObject->getIso3166());
}
}

View File

@ -5,12 +5,14 @@
namespace Alltube\Test;
use Alltube\Config;
use Alltube\PlaylistArchiveStream;
use PHPUnit\Framework\TestCase;
/**
* Unit tests for the ViewFactory class.
*/
class PlaylistArchiveStreamTest extends \PHPUnit_Framework_TestCase
class PlaylistArchiveStreamTest extends TestCase
{
/**
* PlaylistArchiveStream instance.
@ -24,7 +26,12 @@ class PlaylistArchiveStreamTest extends \PHPUnit_Framework_TestCase
*/
protected function setUp()
{
$this->stream = new PlaylistArchiveStream();
if (PHP_OS == 'WINNT') {
$configFile = 'config_test_windows.yml';
} else {
$configFile = 'config_test.yml';
}
$this->stream = new PlaylistArchiveStream(Config::getInstance('config/'.$configFile));
}
/**
@ -88,7 +95,11 @@ class PlaylistArchiveStreamTest extends \PHPUnit_Framework_TestCase
{
$this->stream->stream_open('playlist://BaW_jenozKc;BaW_jenozKc/worst');
while (!$this->stream->stream_eof()) {
$this->assertLessThanOrEqual(8192, strlen($this->stream->stream_read(8192)));
$result = $this->stream->stream_read(8192);
$this->assertInternalType('string', $result);
if (is_string($result)) {
$this->assertLessThanOrEqual(8192, strlen($result));
}
}
}

View File

@ -6,13 +6,14 @@
namespace Alltube\Test;
use Alltube\UglyRouter;
use PHPUnit\Framework\TestCase;
use Slim\Http\Environment;
use Slim\Http\Request;
/**
* Unit tests for the UglyRouter class.
*/
class UglyRouterTest extends \PHPUnit_Framework_TestCase
class UglyRouterTest extends TestCase
{
/**
* UglyRouter instance.

View File

@ -0,0 +1,127 @@
<?php
/**
* VideoDownloadStubsTest class.
*/
namespace Alltube\Test;
use Alltube\Config;
use Alltube\VideoDownload;
use Mockery;
use phpmock\mockery\PHPMockery;
use PHPUnit\Framework\TestCase;
/**
* Unit tests for the VideoDownload class.
* They are in a separate file so they can safely replace PHP functions with stubs.
*/
class VideoDownloadStubsTest extends TestCase
{
/**
* VideoDownload instance.
*
* @var VideoDownload
*/
private $download;
/**
* Config class instance.
*
* @var Config
*/
private $config;
/**
* Video URL used in many tests.
*
* @var string
*/
private $url;
/**
* Initialize properties used by test.
*/
protected function setUp()
{
PHPMockery::mock('Alltube', 'popen');
PHPMockery::mock('Alltube', 'fopen');
if (PHP_OS == 'WINNT') {
$configFile = 'config_test_windows.yml';
} else {
$configFile = 'config_test.yml';
}
$this->config = Config::getInstance('config/'.$configFile);
$this->download = new VideoDownload($this->config);
$this->url = 'https://www.youtube.com/watch?v=XJC9_JkzugE';
}
/**
* Remove stubs.
*
* @return void
*/
protected function tearDown()
{
Mockery::close();
}
/**
* Test getAudioStream function with a buggy popen.
*
* @return void
* @expectedException Exception
*/
public function testGetAudioStreamWithPopenError()
{
$this->download->getAudioStream($this->url, 'best');
}
/**
* Test getM3uStream function with a buggy popen.
*
* @return void
* @expectedException Exception
*/
public function testGetM3uStreamWithPopenError()
{
$this->download->getM3uStream($this->download->getJSON($this->url, 'best'));
}
/**
* Test getRtmpStream function with a buggy popen.
*
* @return void
* @expectedException Exception
*/
public function testGetRtmpStreamWithPopenError()
{
$this->download->getRtmpStream($this->download->getJSON($this->url, 'best'));
}
/**
* Test getRemuxStream function with a buggy popen.
*
* @return void
* @expectedException Exception
*/
public function testGetRemuxStreamWithPopenError()
{
$this->download->getRemuxStream([$this->url, $this->url]);
}
/**
* Test getPlaylistArchiveStream function with a buggy popen.
*
* @return void
* @expectedException Exception
*/
public function testGetPlaylistArchiveStreamWithPopenError()
{
$video = $this->download->getJSON(
'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC',
'best'
);
$this->download->getPlaylistArchiveStream($video, 'best');
}
}

View File

@ -7,11 +7,12 @@ namespace Alltube\Test;
use Alltube\Config;
use Alltube\VideoDownload;
use PHPUnit\Framework\TestCase;
/**
* Unit tests for the VideoDownload class.
*/
class VideoDownloadTest extends \PHPUnit_Framework_TestCase
class VideoDownloadTest extends TestCase
{
/**
* VideoDownload instance.
@ -20,12 +21,25 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
private $download;
/**
* Config class instance.
*
* @var Config
*/
private $config;
/**
* Initialize properties used by test.
*/
protected function setUp()
{
$this->download = new VideoDownload(Config::getInstance('config/config_test.yml'));
if (PHP_OS == 'WINNT') {
$configFile = 'config_test_windows.yml';
} else {
$configFile = 'config_test.yml';
}
$this->config = Config::getInstance('config/'.$configFile);
$this->download = new VideoDownload($this->config);
}
/**
@ -44,9 +58,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
public function testConstructorWithMissingYoutubedl()
{
new VideoDownload(
new Config(['youtubedl' => 'foo'])
);
$this->config->youtubedl = 'foo';
new VideoDownload($this->config);
}
/**
@ -57,9 +70,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
public function testConstructorWithMissingPython()
{
new VideoDownload(
new Config(['python' => 'foo'])
);
$this->config->python = 'foo';
new VideoDownload($this->config);
}
/**
@ -88,8 +100,13 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
* @dataProvider rtmpUrlProvider
* @dataProvider remuxUrlProvider
*/
public function testGetURL($url, $format, $filename, $extension, $domain)
{
public function testGetURL(
$url,
$format,
/* @scrutinizer ignore-unused */ $filename,
/* @scrutinizer ignore-unused */ $extension,
$domain
) {
$videoURL = $this->download->getURL($url, $format);
$this->assertContains($domain, $videoURL[0]);
}
@ -209,7 +226,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
{
return [
[
'https://twitter.com/verge/status/813055465324056576/video/1', 'best',
'https://twitter.com/verge/status/813055465324056576/video/1', 'hls-2176',
'The_Verge_-_This_tiny_origami_robot_can_self-fold_and_complete_tasks-813055465324056576',
'mp4',
'video.twimg.com',
@ -363,23 +380,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
public function testGetAudioStreamAvconvError($url, $format)
{
$download = new VideoDownload(new Config(['avconv' => 'foobar']));
$download->getAudioStream($url, $format);
}
/**
* Test getAudioStream function without curl or rtmpdump.
*
* @param string $url URL
* @param string $format Format
*
* @return void
* @expectedException Exception
* @dataProvider rtmpUrlProvider
*/
public function testGetAudioStreamRtmpError($url, $format)
{
$download = new VideoDownload(new Config(['rtmpdump' => 'foobar']));
$this->config->avconv = 'foobar';
$download = new VideoDownload($this->config);
$download->getAudioStream($url, $format);
}
@ -476,7 +478,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
public function testGetM3uStreamAvconvError($url, $format)
{
$download = new VideoDownload(new Config(['avconv' => 'foobar']));
$this->config->avconv = 'foobar';
$download = new VideoDownload($this->config);
$video = $download->getJSON($url, $format);
$download->getM3uStream($video);
}
@ -485,6 +488,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
* Test getPlaylistArchiveStream function without avconv.
*
* @return void
* @requires OS Linux
*/
public function testGetPlaylistArchiveStream()
{

View File

@ -6,6 +6,7 @@
namespace Alltube\Test;
use Alltube\ViewFactory;
use PHPUnit\Framework\TestCase;
use Slim\Container;
use Slim\Http\Environment;
use Slim\Http\Request;
@ -14,7 +15,7 @@ use Slim\Views\Smarty;
/**
* Unit tests for the ViewFactory class.
*/
class ViewFactoryTest extends \PHPUnit_Framework_TestCase
class ViewFactoryTest extends TestCase
{
/**
* Test the create() function.

View File

@ -3,12 +3,21 @@
* File used to bootstrap tests.
*/
use Alltube\PlaylistArchiveStream;
use phpmock\mockery\PHPMockery;
/**
* Composer autoload.
*/
require_once __DIR__.'/../vendor/autoload.php';
ini_set('session.use_cookies', 0);
session_cache_limiter('');
session_start();
stream_wrapper_register('playlist', PlaylistArchiveStream::class);
/*
* @see https://bugs.php.net/bug.php?id=68541
*/
PHPMockery::define('Alltube', 'popen');
PHPMockery::define('Alltube', 'fopen');

112
yarn.lock
View File

@ -11,13 +11,13 @@ abbrev@1:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
ajv@^5.1.0:
version "5.2.3"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.3.tgz#c06f598778c44c6b161abafe3466b81ad1814ed2"
version "5.3.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda"
dependencies:
co "^4.6.0"
fast-deep-equal "^1.0.0"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
json-stable-stringify "^1.0.1"
alce@1.0.0:
version "1.0.0"
@ -74,7 +74,7 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"
argparse@^1.0.2:
argparse@^1.0.2, argparse@^1.0.7:
version "1.0.9"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
dependencies:
@ -269,9 +269,9 @@ combined-stream@^1.0.5, combined-stream@~1.0.5:
dependencies:
delayed-stream "~1.0.0"
commander@~2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
commander@~2.12.1:
version "2.12.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
compress-commons@^1.2.0:
version "1.2.2"
@ -403,6 +403,10 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
entities@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
error-ex@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
@ -453,6 +457,10 @@ fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
faye-websocket@~0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
@ -636,13 +644,13 @@ grunt-contrib-cssmin@~2.2.1:
clean-css "~4.1.1"
maxmin "^2.1.0"
grunt-contrib-uglify@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/grunt-contrib-uglify/-/grunt-contrib-uglify-3.1.0.tgz#10d1e4849210ec92bf0b08247e24186354d5e9ee"
grunt-contrib-uglify@~3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/grunt-contrib-uglify/-/grunt-contrib-uglify-3.2.1.tgz#8458943eb6053badff829b6f0c9126b1ea624c4a"
dependencies:
chalk "^1.0.0"
maxmin "^1.1.0"
uglify-js "~3.0.4"
uglify-js "~3.2.0"
uri-path "^1.0.0"
grunt-contrib-watch@~1.0.0:
@ -713,6 +721,12 @@ grunt-legacy-util@~1.0.0:
underscore.string "~3.2.3"
which "~1.2.1"
grunt-markdownlint@~1.0.43:
version "1.0.43"
resolved "https://registry.yarnpkg.com/grunt-markdownlint/-/grunt-markdownlint-1.0.43.tgz#afcd3bbab5a5a293bf0050010a7fa9fdea38eca6"
dependencies:
markdownlint "^0.6.1"
grunt-phpcs@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/grunt-phpcs/-/grunt-phpcs-0.4.0.tgz#a08d625fc64465e453b2bd93f810b2a81e94bdaa"
@ -942,20 +956,10 @@ json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
json-stable-stringify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
dependencies:
jsonify "~0.0.0"
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
jsonlint@1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/jsonlint/-/jsonlint-1.6.2.tgz#5737045085f55eb455c68b1ff4ebc01bd50e8830"
@ -978,6 +982,12 @@ lazystream@^1.0.0:
dependencies:
readable-stream "^2.0.5"
linkify-it@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f"
dependencies:
uc.micro "^1.0.1"
livereload-js@^2.2.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.2.2.tgz#6c87257e648ab475bc24ea257457edcc1f8d0bc2"
@ -1015,6 +1025,22 @@ map-obj@^1.0.0, map-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
markdown-it@8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.3.2.tgz#df4b86530d17c3bc9beec3b68d770b92ea17ae96"
dependencies:
argparse "^1.0.7"
entities "~1.1.1"
linkify-it "^2.0.0"
mdurl "^1.0.1"
uc.micro "^1.0.3"
markdownlint@^0.6.1:
version "0.6.4"
resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.6.4.tgz#7fa77e0d8c1b1c3ed7978761ce664bd23e7328ef"
dependencies:
markdown-it "8.3.2"
maxmin@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/maxmin/-/maxmin-1.1.0.tgz#71365e84a99dd8f8b3f7d5fde2f00d1e7f73be61"
@ -1033,6 +1059,10 @@ maxmin@^2.1.0:
gzip-size "^3.0.0"
pretty-bytes "^3.0.0"
mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@ -1095,8 +1125,10 @@ nan@^2.6.2:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
node-abi@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.1.1.tgz#c9cda256ec8aa99bcab2f6446db38af143338b2a"
version "2.1.2"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.1.2.tgz#4da6caceb6685fcd31e7dd1994ef6bb7d0a9c0b2"
dependencies:
semver "^5.4.1"
node-gyp@^3.6.2:
version "3.6.2"
@ -1437,8 +1469,8 @@ request@2:
uuid "^3.1.0"
resolve@^1.1.6:
version "1.4.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86"
version "1.5.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
dependencies:
path-parse "^1.0.5"
@ -1460,7 +1492,7 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
"semver@2 || 3 || 4 || 5":
"semver@2 || 3 || 4 || 5", semver@^5.4.1:
version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
@ -1493,15 +1525,19 @@ simple-get@^1.4.2:
xtend "^4.0.0"
sntp@2.x.x:
version "2.0.2"
resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.0.2.tgz#5064110f0af85f7cfdb7d6b67a40028ce52b4b2b"
version "2.1.0"
resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
dependencies:
hoek "4.x.x"
source-map@0.5.x, source-map@~0.5.1:
source-map@0.5.x:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
spdx-correct@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
@ -1535,8 +1571,8 @@ sshpk@^1.7.0:
tweetnacl "~0.14.0"
statuses@1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
version "1.4.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
stream-buffers@^2.1.0:
version "2.2.0"
@ -1666,12 +1702,16 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
uglify-js@~3.0.4:
version "3.0.28"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.0.28.tgz#96b8495f0272944787b5843a1679aa326640d5f7"
uc.micro@^1.0.1, uc.micro@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192"
uglify-js@~3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.2.2.tgz#870e4b34ed733d179284f9998efd3293f7fd73f6"
dependencies:
commander "~2.11.0"
source-map "~0.5.1"
commander "~2.12.1"
source-map "~0.6.1"
underscore.string@~3.2.3:
version "3.2.3"