Merge branch 'release/0.10.0'

This commit is contained in:
Pierre Rudloff 2017-06-01 22:51:23 +02:00
commit 169a33dd50
53 changed files with 3905 additions and 595 deletions

3
.gitignore vendored
View file

@ -9,6 +9,7 @@ ffmpeg-*/
alltube-*.zip
coverage/
bower_components/
config.yml
config/config.yml
docs/
clover.xml
i18n/*/LC_MESSAGES/*.mo

View file

@ -1,4 +1,4 @@
<ifmodule mod_expires.c>
<ifmodule mod_mime.c>
AddType application/x-web-app-manifest+json .webapp
Addtype font/truetype .ttf
</ifmodule>
@ -15,13 +15,15 @@
FileETag None
RewriteEngine On
<ifmodule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} ^alltube\.herokuapp\.com$ [NC]
RewriteRule ^(.*)$ https://www.alltubedownload.net/$1 [R=301,L]
RewriteCond %{HTTP_HOST} ^alltube\.herokuapp\.com$ [NC]
RewriteRule ^(.*)$ https://www.alltubedownload.net/$1 [R=301,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
</ifmodule>
<ifmodule mod_filter.c>
AddOutputFilterByType DEFLATE text/css text/html application/javascript font/truetype

View file

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

View file

@ -7,7 +7,7 @@ RUN docker-php-ext-install intl
RUN docker-php-ext-install zip
RUN a2enmod rewrite
RUN curl -sS https://getcomposer.org/installer | php
COPY php.ini /usr/local/etc/php/
COPY resources/php.ini /usr/local/etc/php/
COPY . /var/www/html/
RUN php composer.phar install --prefer-dist
RUN npm install

View file

@ -68,7 +68,7 @@ module.exports = function (grunt) {
options: {
archive: 'alltube-<%= githash.main.tag %>.zip'
},
src: ['*.php', '!config.yml', 'dist/**', '.htaccess', 'img/**', 'LICENSE', 'README.md', 'robots.txt', 'sitemap.xml', 'templates/**', 'templates_c/', 'vendor/**', 'classes/**', 'controllers/**', 'bower_components/**', '!vendor/ffmpeg/**', '!vendor/bin/ffmpeg', '!vendor/phpunit/**', '!vendor/squizlabs/**']
src: ['*.php', '!config/config.yml', 'dist/**', '.htaccess', 'img/**', 'LICENSE', 'README.md', 'robots.txt', 'resources/sitemap.xml', 'templates/**', 'templates_c/', 'vendor/**', 'classes/**', 'controllers/**', 'bower_components/**', '!vendor/ffmpeg/**', '!vendor/bin/ffmpeg', '!vendor/phpunit/**', '!vendor/squizlabs/**']
}
},
phpdocumentor: {
@ -80,7 +80,7 @@ module.exports = function (grunt) {
},
jsonlint: {
manifests: {
src: ['*.json', '*.webapp'],
src: ['*.json', 'resources/*.json'],
options: {
format: true
}
@ -90,6 +90,26 @@ module.exports = function (grunt) {
package: {
src: 'package.json'
}
},
potomo: {
dist: {
options: {
poDel: false
},
files: {
'i18n/fr_FR/LC_MESSAGES/Alltube.mo': 'i18n/fr_FR/LC_MESSAGES/Alltube.po',
'i18n/zh_CN/LC_MESSAGES/Alltube.mo': 'i18n/zh_CN/LC_MESSAGES/Alltube.po'
}
}
},
csslint: {
options: {
'box-sizing': false,
'bulletproof-font-face': false
},
css: {
src: 'css/*'
}
}
}
);
@ -105,9 +125,11 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-phpdocumentor');
grunt.loadNpmTasks('grunt-jsonlint');
grunt.loadNpmTasks('grunt-fixpack');
grunt.loadNpmTasks('grunt-potomo');
grunt.loadNpmTasks('grunt-contrib-csslint');
grunt.registerTask('default', ['uglify', 'cssmin']);
grunt.registerTask('lint', ['jslint', 'fixpack', 'jsonlint', 'phpcs']);
grunt.registerTask('default', ['uglify', 'cssmin', 'potomo']);
grunt.registerTask('lint', ['jslint', 'csslint', 'fixpack', 'jsonlint', 'phpcs']);
grunt.registerTask('test', ['phpunit']);
grunt.registerTask('doc', ['phpdocumentor']);
grunt.registerTask('release', ['default', 'githash', 'compress']);

View file

@ -43,12 +43,16 @@ npm install
composer install
```
### On Heroku
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
## Config
If you want to use a custom config, you need to create a config file:
```bash
cp config.example.yml config.yml
cp config/config.example.yml config/config.yml
```
## PHP requirements
@ -168,7 +172,7 @@ You can also have a look at this [example project](https://github.com/Rudloff/al
## FAQ
Please read the [FAQ](FAQ.md) before reporting any issue.
Please read the [FAQ](resources/FAQ.md) before reporting any issue.
## License

38
app.json Normal file
View file

@ -0,0 +1,38 @@
{
"name": "AllTube Download",
"description": "HTML GUI for youtube-dl",
"repository": "https://github.com/Rudloff/alltube.git",
"logo": "https://alltubedownload.net/img/logo.png",
"keywords": [
"alltube",
"download",
"video",
"youtube",
"php"
],
"buildpacks": [
{
"url": "https://github.com/piotras/heroku-buildpack-gettext.git"
},
{
"url": "heroku/nodejs"
},
{
"url": "heroku/python"
},
{
"url": "heroku/php"
}
],
"env": {
"CONVERT": {
"description": "Enable audio conversion",
"value": "true"
},
"PYTHON": {
"description": "Path to python binary",
"value": "/app/.heroku/python/bin/python"
}
},
"website": "https://alltubedownload.net/"
}

View file

@ -38,7 +38,7 @@ class Config
*
* @var array
*/
public $params = ['--no-warnings', '--ignore-errors', '--flat-playlist'];
public $params = ['--no-warnings', '--ignore-errors', '--flat-playlist', '--restrict-filenames'];
/**
* Enable audio conversion.
@ -126,13 +126,13 @@ class Config
*
* @return Config
*/
public static function getInstance($yamlfile = 'config.yml')
public static function getInstance($yamlfile = 'config/config.yml')
{
$yamlPath = __DIR__.'/../'.$yamlfile;
if (is_null(self::$instance) || self::$instance->file != $yamlfile) {
if (is_file($yamlfile)) {
$options = Yaml::parse(file_get_contents($yamlPath));
} elseif ($yamlfile == 'config.yml' || empty($yamlfile)) {
} elseif ($yamlfile == 'config/config.yml' || empty($yamlfile)) {
/*
Allow for the default file to be missing in order to
not surprise users that did not create a config file

95
classes/Locale.php Normal file
View file

@ -0,0 +1,95 @@
<?php
/**
* Locale class.
*/
namespace Alltube;
use Teto\HTTP\AcceptLanguage;
/**
* Class used to represent locales.
*/
class Locale
{
/**
* Locale language.
*
* @var string
*/
private $language;
/**
* Locale region.
*
* @var string
*/
private $region;
/**
* Locale constructor.
*
* @param string $locale ISO 15897 code
*/
public function __construct($locale)
{
$parse = AcceptLanguage::parse($locale);
$this->language = $parse[1]['language'];
$this->region = $parse[1]['region'];
}
/**
* Convert the locale to a string.
*
* @return string ISO 15897 code
*/
public function __toString()
{
return $this->getIso15897();
}
/**
* Get the full name of the locale.
*
* @return string
*/
public function getFullName()
{
return \Locale::getDisplayName($this->getIso15897(), $this->getIso15897());
}
/**
* Get the ISO 15897 code.
*
* @return string
*/
public function getIso15897()
{
return $this->language.'_'.$this->region;
}
/**
* Get the BCP 47 code.
*
* @return string
*/
public function getBcp47()
{
return $this->language.'-'.$this->region;
}
/**
* Get the ISO 3166 code.
*
* @return string
*/
public function getIso3166()
{
return strtolower($this->region);
}
public function getCountry()
{
return country($this->getIso3166());
}
}

87
classes/LocaleManager.php Normal file
View file

@ -0,0 +1,87 @@
<?php
/**
* LocaleManager class.
*/
namespace Alltube;
/**
* Class used to manage locales.
*/
class LocaleManager
{
/**
* Supported locales.
*
* @var array
*/
private $supportedLocales = ['en_US', 'fr_FR', 'zh_CN'];
/**
* Current locale.
*
* @var Locale
*/
private $curLocale;
/**
* Session segment used to store session variables.
*
* @var \Aura\Session\Segment
*/
private $sessionSegment;
/**
* LocaleManager constructor.
*
* @param array $cookies Cookie array
*/
public function __construct(array $cookies = [])
{
$session_factory = new \Aura\Session\SessionFactory();
$session = $session_factory->newInstance($cookies);
$this->sessionSegment = $session->getSegment('Alltube\LocaleManager');
$cookieLocale = $this->sessionSegment->get('locale');
if (isset($cookieLocale)) {
$this->setLocale(new Locale($cookieLocale));
}
}
/**
* Get a list of supported locales.
*
* @return Locale[]
*/
public function getSupportedLocales()
{
$return = [];
foreach ($this->supportedLocales as $supportedLocale) {
$return[] = new Locale($supportedLocale);
}
return $return;
}
/**
* Get the current locale.
*
* @return Locale
*/
public function getLocale()
{
return $this->curLocale;
}
/**
* Set the current locale.
*
* @param Locale $locale Locale
*/
public function setLocale(Locale $locale)
{
putenv('LANG='.$locale);
setlocale(LC_ALL, [$locale, $locale.'.utf8']);
$this->curLocale = $locale;
$this->sessionSegment->set('locale', $locale);
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* LocaleMiddleware class.
*/
namespace Alltube;
use Psr\Container\ContainerInterface;
use Slim\Http\Request;
use Slim\Http\Response;
use Teto\HTTP\AcceptLanguage;
/**
* Detect user locale.
*/
class LocaleMiddleware
{
/**
* LocaleManager instance.
*
* @var LocaleManager
*/
private $localeManager;
/**
* LocaleMiddleware constructor.
*
* @param ContainerInterface $container Slim dependency container
*/
public function __construct(ContainerInterface $container)
{
$this->localeManager = $container->get('locale');
}
/**
* Test if a locale can be used for the current user.
*
* @param array $proposedLocale Locale array created by AcceptLanguage::parse()
*
* @return string Locale name if chosen, nothing otherwise
*/
public function testLocale(array $proposedLocale)
{
foreach ($this->localeManager->getSupportedLocales() as $locale) {
$parsedLocale = AcceptLanguage::parse($locale);
if (isset($proposedLocale['language'])
&& $parsedLocale[1]['language'] == $proposedLocale['language']
&& $parsedLocale[1]['region'] == $proposedLocale['region']
) {
return new Locale($proposedLocale['language'].'_'.$proposedLocale['region']);
}
}
}
/**
* Main middleware function.
*
* @param Request $request PSR request
* @param Response $response PSR response
* @param callable $next Next middleware
*
* @return Response
*/
public function __invoke(Request $request, Response $response, callable $next)
{
$headers = $request->getHeader('Accept-Language');
$curLocale = $this->localeManager->getLocale();
if (!isset($curLocale)) {
if (isset($headers[0])) {
$this->localeManager->setLocale(
AcceptLanguage::detect([$this, 'testLocale'], new Locale('en_US'), $headers[0])
);
} else {
$this->localeManager->setLocale(new Locale('en_US'));
}
}
return $next($request, $response);
}
}

View file

@ -0,0 +1,201 @@
<?php
/**
* PlaylistArchiveStream class.
*
* @codingStandardsIgnoreFile
*/
namespace Alltube;
use Barracuda\ArchiveStream\TarArchive;
/**
* Class used to create a Tar archive from playlists and stream it to the browser.
*
* @link http://php.net/manual/en/class.streamwrapper.php
*/
class PlaylistArchiveStream extends TarArchive
{
/**
* Files to add in the archive.
*
* @var array[]
*/
private $files;
/**
* Stream used to store data before it is sent to the browser.
*
* @var resource
*/
private $buffer;
/**
* Guzzle client.
*
* @var \GuzzleHttp\Client
*/
private $client;
/**
* VideoDownload instance.
*
* @var VideoDownload
*/
private $download;
/**
* Current file position in $files array.
*
* @var int
*/
private $curFile = 0;
/**
* Video format to download.
*
* @var string
*/
private $format;
/**
* PlaylistArchiveStream constructor.
*/
public function __construct()
{
$this->client = new \GuzzleHttp\Client();
$this->download = new VideoDownload();
}
/**
* Add data to the archive.
*
* @param string $data Data
*
* @return void
*/
protected function send($data)
{
$pos = ftell($this->buffer);
fwrite($this->buffer, $data);
fseek($this->buffer, $pos);
}
/**
* Called when fopen() is used on the stream.
*
* @param string $path Playlist path (should be playlist://url1;url2;.../format)
*
* @return bool
*/
public function stream_open($path)
{
$this->format = ltrim(parse_url($path, PHP_URL_PATH), '/');
$this->buffer = fopen('php://temp', 'r+');
foreach (explode(';', parse_url($path, PHP_URL_HOST)) as $url) {
$this->files[] = [
'url' => urldecode($url),
'headersSent'=> false,
'complete' => false,
'stream' => null,
];
}
return true;
}
/**
* Called when fwrite() is used on the stream.
*
* @return int
*/
public function stream_write()
{
//We don't support writing to a stream
return 0;
}
/**
* Called when fstat() is used on the stream.
*
* @return array
*/
public function stream_stat()
{
//We need this so Slim won't try to get the size of the stream
return [
'mode'=> 0010000,
];
}
/**
* Called when ftell() is used on the stream.
*
* @return int
*/
public function stream_tell()
{
return ftell($this->buffer);
}
/**
* Called when fseek() is used on the stream.
*
* @param int $offset Offset
*
* @return bool
*/
public function stream_seek($offset)
{
return fseek($this->buffer, $offset) == 0;
}
/**
* Called when feof() is used on the stream.
*
* @return bool
*/
public function stream_eof()
{
foreach ($this->files as $file) {
if (!$file['complete']) {
return false;
}
}
return true;
}
/**
* Called when fread() is used on the stream.
*
* @param int $count Number of bytes to read
*
* @return string
*/
public function stream_read($count)
{
if (!$this->files[$this->curFile]['headersSent']) {
$urls = $this->download->getUrl($this->files[$this->curFile]['url'], $this->format);
$response = $this->client->request('GET', $urls[0], ['stream' => true]);
$contentLengthHeaders = $response->getHeader('Content-Length');
$this->init_file_stream_transfer(
$this->download->getFilename($this->files[$this->curFile]['url'], $this->format),
$contentLengthHeaders[0]
);
$this->files[$this->curFile]['headersSent'] = true;
$this->files[$this->curFile]['stream'] = $response->getBody();
} elseif (!$this->files[$this->curFile]['stream']->eof()) {
$this->stream_file_part($this->files[$this->curFile]['stream']->read($count));
} elseif (!$this->files[$this->curFile]['complete']) {
$this->complete_file_stream();
$this->files[$this->curFile]['complete'] = true;
} elseif (isset($this->files[$this->curFile])) {
$this->curFile += 1;
}
return fread($this->buffer, $count);
}
}

View file

@ -218,9 +218,9 @@ class VideoDownload
*
* @return \Symfony\Component\Process\Process Process
*/
private function getRtmpProcess($video)
private function getRtmpProcess(\stdClass $video)
{
if (!shell_exec('which '.$this->config->rtmpdump)) {
if (!$this->checkCommand([$this->config->rtmpdump, '--help'])) {
throw(new \Exception('Can\'t find rtmpdump'));
}
$builder = new ProcessBuilder(
@ -240,6 +240,22 @@ class VideoDownload
return $builder->getProcess();
}
/**
* Check if a command runs successfully.
*
* @param array $command Command and arguments
*
* @return bool False if the command returns an error, true otherwise
*/
private function checkCommand(array $command)
{
$builder = ProcessBuilder::create($command);
$process = $builder->getProcess();
$process->run();
return $process->isSuccessful();
}
/**
* Get a process that runs avconv in order to convert a video to MP3.
*
@ -249,11 +265,11 @@ class VideoDownload
*/
private function getAvconvMp3Process($url)
{
if (!shell_exec('which '.$this->config->avconv)) {
if (!$this->checkCommand([$this->config->avconv, '-version'])) {
throw(new \Exception('Can\'t find avconv or ffmpeg'));
}
return ProcessBuilder::create(
$builder = ProcessBuilder::create(
[
$this->config->avconv,
'-v', 'quiet',
@ -265,6 +281,8 @@ class VideoDownload
'pipe:1',
]
);
return $builder->getProcess();
}
/**
@ -292,7 +310,7 @@ class VideoDownload
} else {
$avconvProc = $this->getAvconvMp3Process($video->url);
return popen($avconvProc->getProcess()->getCommandLine(), 'r');
return popen($avconvProc->getCommandLine(), 'r');
}
}
@ -305,7 +323,7 @@ class VideoDownload
*/
public function getM3uStream(\stdClass $video)
{
if (!shell_exec('which '.$this->config->avconv)) {
if (!$this->checkCommand([$this->config->avconv, '-version'])) {
throw(new \Exception('Can\'t find avconv or ffmpeg'));
}
@ -362,4 +380,23 @@ class VideoDownload
{
return popen($this->getRtmpProcess($video)->getCommandLine(), 'r');
}
/**
* Get a Tar stream containing every video in the playlist piped through the server.
*
* @param object $video Video object returned by youtube-dl
* @param string $format Requested format
*
* @return Response HTTP response
*/
public function getPlaylistArchiveStream(\stdClass $video, $format)
{
$playlistItems = [];
foreach ($video->entries as $entry) {
$playlistItems[] = urlencode($entry->url);
}
$stream = fopen('playlist://'.implode(';', $playlistItems).'/'.$format, 'r');
return $stream;
}
}

View file

@ -12,22 +12,25 @@
"symfony/process": "~3.2.0",
"ptachoire/process-builder-chain": "~1.2.0",
"guzzlehttp/guzzle": "~6.2.0",
"rudloff/rtmpdump-bin": "~2.3",
"aura/session": "~2.1.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"
},
"require-dev": {
"symfony/var-dumper": "~3.2.0",
"squizlabs/php_codesniffer": "~2.8.0",
"squizlabs/php_codesniffer": "~3.0.0",
"phpunit/phpunit": "~5.7.2",
"ffmpeg/ffmpeg": "dev-release",
"rg3/youtube-dl": "~2017.04.28",
"rudloff/rtmpdump-bin": "~2.3",
"rg3/youtube-dl": "~2017.05.09",
"rudloff/rtmpdump-bin": "~2.3.0",
"heroku/heroku-buildpack-php": "*"
},
"extra": {
"paas": {
"nginx-includes": [
"nginx.conf"
"resources/nginx.conf"
]
}
},
@ -36,10 +39,10 @@
"type": "package",
"package": {
"name": "rg3/youtube-dl",
"version": "2017.04.28",
"version": "2017.05.09",
"dist": {
"type": "zip",
"url": "https://github.com/rg3/youtube-dl/archive/2017.04.28.zip"
"url": "https://github.com/rg3/youtube-dl/archive/2017.05.09.zip"
}
}
},

419
composer.lock generated
View file

@ -4,8 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "0a4bd158955df2e3f5ea89714ba9e740",
"content-hash": "cb0e773496b6f26caf1c8591f23488f5",
"content-hash": "22e16312bcf339c90850660d9e5923e7",
"packages": [
{
"name": "aura/session",
@ -67,7 +66,47 @@
"session",
"sessions"
],
"time": "2016-10-03 20:28:32"
"time": "2016-10-03T20:28:32+00:00"
},
{
"name": "barracudanetworks/archivestream-php",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/barracudanetworks/ArchiveStream-php.git",
"reference": "1bf98097d1e9b137fd40081f26abb0a17b097ef7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barracudanetworks/ArchiveStream-php/zipball/1bf98097d1e9b137fd40081f26abb0a17b097ef7",
"reference": "1bf98097d1e9b137fd40081f26abb0a17b097ef7",
"shasum": ""
},
"require": {
"ext-gmp": "*",
"ext-mbstring": "*",
"php": ">=5.1.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Barracuda\\ArchiveStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A library for dynamically streaming dynamic tar or zip files without the need to have the complete file stored on the server.",
"homepage": "https://github.com/barracudanetworks/ArchiveStream-php",
"keywords": [
"archive",
"php",
"stream",
"tar",
"zip"
],
"time": "2017-01-13T14:52:38+00:00"
},
{
"name": "container-interop/container-interop",
@ -98,7 +137,7 @@
],
"description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
"homepage": "https://github.com/container-interop/container-interop",
"time": "2017-02-14 19:40:03"
"time": "2017-02-14T19:40:03+00:00"
},
{
"name": "guzzlehttp/guzzle",
@ -160,7 +199,7 @@
"rest",
"web service"
],
"time": "2017-02-28 22:50:30"
"time": "2017-02-28T22:50:30+00:00"
},
{
"name": "guzzlehttp/promises",
@ -211,7 +250,7 @@
"keywords": [
"promise"
],
"time": "2016-12-20 10:07:11"
"time": "2016-12-20T10:07:11+00:00"
},
{
"name": "guzzlehttp/psr7",
@ -276,7 +315,7 @@
"uri",
"url"
],
"time": "2017-03-20 17:10:46"
"time": "2017-03-20T17:10:46+00:00"
},
{
"name": "mathmarques/smarty-view",
@ -326,7 +365,7 @@
"template",
"view"
],
"time": "2016-08-25 19:04:49"
"time": "2016-08-25T19:04:49+00:00"
},
{
"name": "nikic/fast-route",
@ -369,7 +408,7 @@
"router",
"routing"
],
"time": "2017-01-19 11:35:12"
"time": "2017-01-19T11:35:12+00:00"
},
{
"name": "pimple/pimple",
@ -415,7 +454,7 @@
"container",
"dependency injection"
],
"time": "2015-09-11 15:10:35"
"time": "2015-09-11T15:10:35+00:00"
},
{
"name": "psr/container",
@ -464,7 +503,7 @@
"container-interop",
"psr"
],
"time": "2017-02-14 16:28:37"
"time": "2017-02-14T16:28:37+00:00"
},
{
"name": "psr/http-message",
@ -514,7 +553,7 @@
"request",
"response"
],
"time": "2016-08-06 14:39:51"
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "ptachoire/process-builder-chain",
@ -550,35 +589,81 @@
}
],
"description": "Add ability to chain symfony processes",
"time": "2016-04-10 08:33:20"
"time": "2016-04-10T08:33:20+00:00"
},
{
"name": "rudloff/rtmpdump-bin",
"version": "2.3",
"name": "rinvex/country",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/Rudloff/rtmpdump-bin.git",
"reference": "133cdd80e3bab66593e88a5276158596383afd97"
"url": "https://github.com/rinvex/country.git",
"reference": "9405da035644bc76671bfba0c282fef8fd3408e1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Rudloff/rtmpdump-bin/zipball/133cdd80e3bab66593e88a5276158596383afd97",
"reference": "133cdd80e3bab66593e88a5276158596383afd97",
"url": "https://api.github.com/repos/rinvex/country/zipball/9405da035644bc76671bfba0c282fef8fd3408e1",
"reference": "9405da035644bc76671bfba0c282fef8fd3408e1",
"shasum": ""
},
"require-dev": {
"rtmpdump/rtmpdump": "2.3"
"require": {
"php": ">=5.5.9"
},
"require-dev": {
"phpunit/phpunit": "^5.2.0"
},
"bin": [
"rtmpdump"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Rinvex\\Country\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0"
"MIT"
],
"description": "rtmpdump binary for Linux 64 bit",
"time": "2016-04-12 19:17:32"
"authors": [
{
"name": "Rinvex LLC",
"email": "help@rinvex.com",
"homepage": "https://rinvex.com"
},
{
"name": "Abdelrahman Omran",
"email": "me@omranic.com",
"homepage": "https://omranic.com",
"role": "Project Lead"
},
{
"name": "The Generous Laravel Community",
"homepage": "https://github.com/rinvex/country/contributors"
}
],
"description": "Rinvex Country is a simple and lightweight package for retrieving country details with flexibility. A whole bunch of data including name, demonym, capital, iso codes, dialling codes, geo data, currencies, flags, emoji, and other attributes for all 250 countries worldwide at your fingertips.",
"homepage": "https://rinvex.com",
"keywords": [
"Flexible",
"Simple",
"countries",
"country",
"currencies",
"demonym",
"dialling",
"emoji",
"flags",
"geographic",
"languages",
"rinvex",
"svg"
],
"time": "2016-11-27T05:53:11+00:00"
},
{
"name": "slim/slim",
@ -649,7 +734,61 @@
"micro",
"router"
],
"time": "2017-03-19 17:55:20"
"time": "2017-03-19T17:55:20+00:00"
},
{
"name": "smarty-gettext/smarty-gettext",
"version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/smarty-gettext/smarty-gettext.git",
"reference": "00fe2fcbc41e24e0245cd9d73f96bc7b0337972d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/smarty-gettext/smarty-gettext/zipball/00fe2fcbc41e24e0245cd9d73f96bc7b0337972d",
"reference": "00fe2fcbc41e24e0245cd9d73f96bc7b0337972d",
"shasum": ""
},
"require": {
"ext-gettext": "*",
"ext-pcre": "*",
"php": "~5.3|~7.0"
},
"require-dev": {
"azatoth/php-pgettext": "~1.0",
"smarty/smarty": "3.1.*"
},
"suggest": {
"azatoth/php-pgettext": "Support msgctxt for {t} via context parameter"
},
"bin": [
"tsmarty2c.php"
],
"type": "library",
"autoload": {
"files": [
"block.t.php",
"function.locale.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1+"
],
"authors": [
{
"name": "Sagi Bashari",
"email": "sagi@boom.org.il"
},
{
"name": "Elan Ruusamäe",
"email": "glen@delfi.ee"
}
],
"description": "Gettext plugin enabling internationalization in Smarty Package files",
"homepage": "https://github.com/smarty-gettext/smarty-gettext",
"time": "2017-05-12T12:14:46+00:00"
},
{
"name": "smarty/smarty",
@ -702,20 +841,20 @@
"keywords": [
"templating"
],
"time": "2016-12-14 21:57:25"
"time": "2016-12-14T21:57:25+00:00"
},
{
"name": "symfony/process",
"version": "v3.2.8",
"version": "v3.2.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0"
"reference": "36774717bbd1631be2d0a45acf48aecd5836c867"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0",
"reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0",
"url": "https://api.github.com/repos/symfony/process/zipball/36774717bbd1631be2d0a45acf48aecd5836c867",
"reference": "36774717bbd1631be2d0a45acf48aecd5836c867",
"shasum": ""
},
"require": {
@ -751,20 +890,20 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2017-04-12 14:13:17"
"time": "2017-05-08T01:51:21+00:00"
},
{
"name": "symfony/yaml",
"version": "v3.2.8",
"version": "v3.2.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6"
"reference": "4cdb9fec28fba88203a71f6d095018867f7a2065"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/acec26fcf7f3031e094e910b94b002fa53d4e4d6",
"reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6",
"url": "https://api.github.com/repos/symfony/yaml/zipball/4cdb9fec28fba88203a71f6d095018867f7a2065",
"reference": "4cdb9fec28fba88203a71f6d095018867f7a2065",
"shasum": ""
},
"require": {
@ -806,7 +945,48 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2017-05-01 14:55:58"
"time": "2017-05-25T23:42:36+00:00"
},
{
"name": "zonuexe/http-accept-language",
"version": "0.4.1",
"source": {
"type": "git",
"url": "https://github.com/BaguettePHP/http-accept-language.git",
"reference": "f71422b1200737aa9d7c7fa83f07cbe4616198d5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/BaguettePHP/http-accept-language/zipball/f71422b1200737aa9d7c7fa83f07cbe4616198d5",
"reference": "f71422b1200737aa9d7c7fa83f07cbe4616198d5",
"shasum": ""
},
"require": {
"ext-intl": "*"
},
"require-dev": {
"phploc/phploc": "*",
"phpunit/phpunit": "4.1.*",
"theseer/phpdox": "0.6.*"
},
"type": "library",
"autoload": {
"psr-4": {
"Teto\\HTTP\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "USAMI Kenta",
"email": "tadsan@zonu.me"
}
],
"description": "HTTP Accept-Language Header parser",
"time": "2014-10-19T09:22:18+00:00"
}
],
"packages-dev": [
@ -862,7 +1042,7 @@
"constructor",
"instantiate"
],
"time": "2015-06-14 21:17:01"
"time": "2015-06-14T21:17:01+00:00"
},
{
"name": "ffmpeg/ffmpeg",
@ -920,7 +1100,7 @@
"nginx",
"php"
],
"time": "2017-03-27 23:33:27"
"time": "2017-03-27T23:33:27+00:00"
},
{
"name": "myclabs/deep-copy",
@ -962,7 +1142,7 @@
"object",
"object graph"
],
"time": "2017-04-12 18:52:22"
"time": "2017-04-12T18:52:22+00:00"
},
{
"name": "phpdocumentor/reflection-common",
@ -1016,7 +1196,7 @@
"reflection",
"static analysis"
],
"time": "2015-12-27 11:43:31"
"time": "2015-12-27T11:43:31+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
@ -1061,7 +1241,7 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"time": "2016-09-30 07:12:33"
"time": "2016-09-30T07:12:33+00:00"
},
{
"name": "phpdocumentor/type-resolver",
@ -1108,7 +1288,7 @@
"email": "me@mikevanriel.com"
}
],
"time": "2016-11-25 06:54:22"
"time": "2016-11-25T06:54:22+00:00"
},
{
"name": "phpspec/prophecy",
@ -1171,7 +1351,7 @@
"spy",
"stub"
],
"time": "2017-03-02 20:05:34"
"time": "2017-03-02T20:05:34+00:00"
},
{
"name": "phpunit/php-code-coverage",
@ -1234,7 +1414,7 @@
"testing",
"xunit"
],
"time": "2017-04-02 07:44:40"
"time": "2017-04-02T07:44:40+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -1281,7 +1461,7 @@
"filesystem",
"iterator"
],
"time": "2016-10-03 07:40:28"
"time": "2016-10-03T07:40:28+00:00"
},
{
"name": "phpunit/php-text-template",
@ -1322,7 +1502,7 @@
"keywords": [
"template"
],
"time": "2015-06-21 13:50:34"
"time": "2015-06-21T13:50:34+00:00"
},
{
"name": "phpunit/php-timer",
@ -1371,7 +1551,7 @@
"keywords": [
"timer"
],
"time": "2017-02-26 11:10:40"
"time": "2017-02-26T11:10:40+00:00"
},
{
"name": "phpunit/php-token-stream",
@ -1420,20 +1600,20 @@
"keywords": [
"tokenizer"
],
"time": "2017-02-27 10:12:30"
"time": "2017-02-27T10:12:30+00:00"
},
{
"name": "phpunit/phpunit",
"version": "5.7.19",
"version": "5.7.20",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1"
"reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69c4f49ff376af2692bad9cebd883d17ebaa98a1",
"reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3cb94a5f8c07a03c8b7527ed7468a2926203f58b",
"reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b",
"shasum": ""
},
"require": {
@ -1451,7 +1631,7 @@
"phpunit/php-timer": "^1.0.6",
"phpunit/phpunit-mock-objects": "^3.2",
"sebastian/comparator": "^1.2.4",
"sebastian/diff": "~1.2",
"sebastian/diff": "^1.4.3",
"sebastian/environment": "^1.3.4 || ^2.0",
"sebastian/exporter": "~2.0",
"sebastian/global-state": "^1.1",
@ -1502,7 +1682,7 @@
"testing",
"xunit"
],
"time": "2017-04-03 02:22:27"
"time": "2017-05-22T07:42:55+00:00"
},
{
"name": "phpunit/phpunit-mock-objects",
@ -1561,19 +1741,47 @@
"mock",
"xunit"
],
"time": "2016-12-08 20:27:08"
"time": "2016-12-08T20:27:08+00:00"
},
{
"name": "rg3/youtube-dl",
"version": "2017.04.28",
"version": "2017.05.09",
"dist": {
"type": "zip",
"url": "https://github.com/rg3/youtube-dl/archive/2017.04.28.zip",
"url": "https://github.com/rg3/youtube-dl/archive/2017.05.09.zip",
"reference": null,
"shasum": null
},
"type": "library"
},
{
"name": "rudloff/rtmpdump-bin",
"version": "2.3",
"source": {
"type": "git",
"url": "https://github.com/Rudloff/rtmpdump-bin.git",
"reference": "133cdd80e3bab66593e88a5276158596383afd97"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Rudloff/rtmpdump-bin/zipball/133cdd80e3bab66593e88a5276158596383afd97",
"reference": "133cdd80e3bab66593e88a5276158596383afd97",
"shasum": ""
},
"require-dev": {
"rtmpdump/rtmpdump": "2.3"
},
"bin": [
"rtmpdump"
],
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0"
],
"description": "rtmpdump binary for Linux 64 bit",
"time": "2016-04-12T19:17:32+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
"version": "1.0.1",
@ -1617,7 +1825,7 @@
],
"description": "Looks up which function or method a line of code belongs to",
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"time": "2017-03-04 06:30:41"
"time": "2017-03-04T06:30:41+00:00"
},
{
"name": "sebastian/comparator",
@ -1681,27 +1889,27 @@
"compare",
"equality"
],
"time": "2017-01-29 09:50:25"
"time": "2017-01-29T09:50:25+00:00"
},
{
"name": "sebastian/diff",
"version": "1.4.1",
"version": "1.4.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
"reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
"reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
"reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
"php": "^5.3.3 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "~4.8"
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
},
"type": "library",
"extra": {
@ -1733,7 +1941,7 @@
"keywords": [
"diff"
],
"time": "2015-12-08 07:14:41"
"time": "2017-05-22T07:24:03+00:00"
},
{
"name": "sebastian/environment",
@ -1783,7 +1991,7 @@
"environment",
"hhvm"
],
"time": "2016-11-26 07:53:53"
"time": "2016-11-26T07:53:53+00:00"
},
{
"name": "sebastian/exporter",
@ -1850,7 +2058,7 @@
"export",
"exporter"
],
"time": "2016-11-19 08:54:04"
"time": "2016-11-19T08:54:04+00:00"
},
{
"name": "sebastian/global-state",
@ -1901,7 +2109,7 @@
"keywords": [
"global state"
],
"time": "2015-10-12 03:26:01"
"time": "2015-10-12T03:26:01+00:00"
},
{
"name": "sebastian/object-enumerator",
@ -1947,7 +2155,7 @@
],
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"time": "2017-02-18 15:18:39"
"time": "2017-02-18T15:18:39+00:00"
},
{
"name": "sebastian/recursion-context",
@ -2000,7 +2208,7 @@
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2016-11-19 07:33:16"
"time": "2016-11-19T07:33:16+00:00"
},
{
"name": "sebastian/resource-operations",
@ -2042,7 +2250,7 @@
],
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"time": "2015-07-28 20:34:47"
"time": "2015-07-28T20:34:47+00:00"
},
{
"name": "sebastian/version",
@ -2085,68 +2293,41 @@
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"time": "2016-10-03 07:35:21"
"time": "2016-10-03T07:35:21+00:00"
},
{
"name": "squizlabs/php_codesniffer",
"version": "2.8.1",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
"reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d"
"reference": "b95ff2c3b122a3ee4b57d149a57d2afce65522c3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d",
"reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b95ff2c3b122a3ee4b57d149a57d2afce65522c3",
"reference": "b95ff2c3b122a3ee4b57d149a57d2afce65522c3",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": ">=5.1.2"
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"bin": [
"scripts/phpcs",
"scripts/phpcbf"
"bin/phpcs",
"bin/phpcbf"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
"dev-master": "3.x-dev"
}
},
"autoload": {
"classmap": [
"CodeSniffer.php",
"CodeSniffer/CLI.php",
"CodeSniffer/Exception.php",
"CodeSniffer/File.php",
"CodeSniffer/Fixer.php",
"CodeSniffer/Report.php",
"CodeSniffer/Reporting.php",
"CodeSniffer/Sniff.php",
"CodeSniffer/Tokens.php",
"CodeSniffer/Reports/",
"CodeSniffer/Tokenizers/",
"CodeSniffer/DocGenerators/",
"CodeSniffer/Standards/AbstractPatternSniff.php",
"CodeSniffer/Standards/AbstractScopeSniff.php",
"CodeSniffer/Standards/AbstractVariableSniff.php",
"CodeSniffer/Standards/IncorrectPatternException.php",
"CodeSniffer/Standards/Generic/Sniffs/",
"CodeSniffer/Standards/MySource/Sniffs/",
"CodeSniffer/Standards/PEAR/Sniffs/",
"CodeSniffer/Standards/PSR1/Sniffs/",
"CodeSniffer/Standards/PSR2/Sniffs/",
"CodeSniffer/Standards/Squiz/Sniffs/",
"CodeSniffer/Standards/Zend/Sniffs/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
@ -2163,7 +2344,7 @@
"phpcs",
"standards"
],
"time": "2017-03-01 22:17:45"
"time": "2017-05-04T00:33:04+00:00"
},
{
"name": "symfony/polyfill-mbstring",
@ -2222,20 +2403,20 @@
"portable",
"shim"
],
"time": "2016-11-14 01:06:16"
"time": "2016-11-14T01:06:16+00:00"
},
{
"name": "symfony/var-dumper",
"version": "v3.2.8",
"version": "v3.2.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8"
"reference": "eda0d0891a0b60a25015f7b85ea8040b51d72e1e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa47963ac7979ddbd42b2d646d1b056bddbf7bb8",
"reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/eda0d0891a0b60a25015f7b85ea8040b51d72e1e",
"reference": "eda0d0891a0b60a25015f7b85ea8040b51d72e1e",
"shasum": ""
},
"require": {
@ -2290,7 +2471,7 @@
"debug",
"dump"
],
"time": "2017-05-01 14:55:58"
"time": "2017-05-15T12:02:31+00:00"
},
{
"name": "webmozart/assert",
@ -2340,7 +2521,7 @@
"check",
"validate"
],
"time": "2016-11-23 20:04:58"
"time": "2016-11-23T20:04:58+00:00"
}
],
"aliases": [],

View file

@ -6,6 +6,7 @@
namespace Alltube\Controller;
use Alltube\Config;
use Alltube\Locale;
use Alltube\PasswordException;
use Alltube\VideoDownload;
use Psr\Container\ContainerInterface;
@ -61,6 +62,13 @@ class FrontController
*/
private $defaultFormat = 'best[protocol^=http]';
/**
* LocaleManager instance.
*
* @var LocaleManager
*/
private $localeManager;
/**
* FrontController constructor.
*
@ -78,6 +86,7 @@ class FrontController
$this->download = new VideoDownload();
$this->container = $container;
$this->view = $this->container->get('view');
$this->localeManager = $this->container->get('locale');
$session_factory = new \Aura\Session\SessionFactory();
$session = $session_factory->newInstance($cookies);
$this->sessionSegment = $session->getSegment('Alltube\Controller\FrontController');
@ -101,18 +110,35 @@ class FrontController
$response,
'index.tpl',
[
'convert' => $this->config->convert,
'uglyUrls' => $this->config->uglyUrls,
'config' => $this->config,
'class' => 'index',
'description' => 'Easily download videos from Youtube, Dailymotion, Vimeo and other websites.',
'domain' => $uri->getScheme().'://'.$uri->getAuthority(),
'canonical' => $this->getCanonicalUrl($request),
'supportedLocales' => $this->localeManager->getSupportedLocales(),
'locale' => $this->localeManager->getLocale(),
]
);
return $response;
}
/**
* Switch locale.
*
* @param Request $request PSR-7 request
* @param Response $response PSR-7 response
* @param array $data Query parameters
*
* @return Response
*/
public function locale(Request $request, Response $response, array $data)
{
$this->localeManager->setLocale(new Locale($data['locale']));
return $response->withRedirect($this->container->get('router')->pathFor('index'));
}
/**
* Display a list of extractors.
*
@ -133,6 +159,7 @@ class FrontController
'description' => 'List of all supported websites from which Alltube Download '.
'can extract video or audio files',
'canonical' => $this->getCanonicalUrl($request),
'locale' => $this->localeManager->getLocale(),
]
);
@ -157,6 +184,7 @@ class FrontController
'title' => 'Password prompt',
'description' => 'You need a password in order to download this video with Alltube Download',
'canonical' => $this->getCanonicalUrl($request),
'locale' => $this->localeManager->getLocale(),
]
);
@ -247,8 +275,7 @@ class FrontController
'protocol' => $protocol,
'config' => $this->config,
'canonical' => $this->getCanonicalUrl($request),
'uglyUrls' => $this->config->uglyUrls,
'remux' => $this->config->remux,
'locale' => $this->localeManager->getLocale(),
]
);
@ -300,6 +327,7 @@ class FrontController
'class' => 'video',
'title' => 'Error',
'canonical' => $this->getCanonicalUrl($request),
'locale' => $this->localeManager->getLocale(),
]
);
@ -320,26 +348,32 @@ class FrontController
private function getStream($url, $format, Response $response, Request $request, $password = null)
{
$video = $this->download->getJSON($url, $format, $password);
if ($video->protocol == 'rtmp') {
if (isset($video->entries)) {
$stream = $this->download->getPlaylistArchiveStream($video, $format);
$response = $response->withHeader('Content-Type', 'application/x-tar');
$response = $response->withHeader(
'Content-Disposition',
'attachment; filename="'.$video->title.'.tar"'
);
return $response->withBody(new Stream($stream));
} elseif ($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));
}
$body = new Stream($stream);
} elseif ($video->protocol == 'm3u8') {
$stream = $this->download->getM3uStream($video);
$response = $response->withHeader('Content-Type', 'video/'.$video->ext);
if ($request->isGet()) {
$response = $response->withBody(new Stream($stream));
}
$body = new Stream($stream);
} else {
$client = new \GuzzleHttp\Client();
$stream = $client->request('GET', $video->url, ['stream' => true]);
$response = $response->withHeader('Content-Type', $stream->getHeader('Content-Type'));
$response = $response->withHeader('Content-Length', $stream->getHeader('Content-Length'));
if ($request->isGet()) {
$response = $response->withBody($stream->getBody());
$body = $stream->getBody();
}
if ($request->isGet()) {
$response = $response->withBody($body);
}
$response = $response->withHeader(
'Content-Disposition',
@ -353,7 +387,7 @@ class FrontController
/**
* Get a remuxed stream piped through the server.
*
* @param array $urls URLs of the video and audio files
* @param string[] $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
@ -429,6 +463,10 @@ class FrontController
$this->sessionSegment->getFlash($url)
);
} else {
if (empty($videoUrls[0])) {
throw new \Exception("Can't find URL of video");
}
return $response->withRedirect($videoUrls[0]);
}
}

View file

@ -10,3 +10,11 @@
font-weight: 400;
src: local('Open Sans'), local('OpenSans'), url(../bower_components/opensans-googlefont/OpenSans-Regular.ttf);
}
.small-font {
font-size: 13px;
}
.large-font {
font-size:24px;
}

View file

@ -1,119 +1,118 @@
body {
text-align:center;
background-color: #EBEBEB;
background-image:url('../img/fond.jpg');
font-family: 'Open Sans', sans-serif;
font-weight:400;
text-align:center;
}
/************************HEADER******************************/
/* Header */
header {
position:absolute;
top:0;
text-align:right;
width:100%;
padding:0;
}
position:absolute;
text-align:right;
top:0;
width:100%;
}
.social
{padding-right:21px;}
.social {
padding-right:21px;
}
header a
{
overflow:hidden;
height:38px;
width:38px;
position:relative;
float:right;
margin-top:13px;
margin-left:13px;
margin-right:0;
background-position:0 0;
background-repeat:no-repeat;
header .social a {
background-position:0 0;
background-repeat:no-repeat;
float:right;
height:38px;
margin-left:13px;
margin-right:0;
margin-top:13px;
overflow:hidden;
position:relative;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
width:38px;
}
header a:focus,
header a:hover
{
outline:none;
header a:hover {
background-position:0 100%;
outline:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
}
.share
{background-image:url('../img/share.png');}
.sharemask
{
height:38px;
width:38px;
position:absolute;
top:0;
left:0;
z-index:10;
background-image:url('../img/sharemask.png');
background-position:top left;
background-repeat:no-repeat;
.share {
background-image:url('../img/share.png');
}
.facebook
{background-image:url('../img/facebook.png');}
.sharemask {
background-image:url('../img/sharemask.png');
background-position:top left;
background-repeat:no-repeat;
height:38px;
left:0;
position:absolute;
top:0;
width:38px;
z-index:10;
.facebookmask
{
height:38px;
width:38px;
position:absolute;
top:0;
left:0;
z-index:10;
background-image:url('../img/facebookmask.png');
background-position:top left;
background-repeat:no-repeat;
}
.twitter
{background-image:url('../img/twitter.png');}
.facebook {
background-image:url('../img/facebook.png');
}
.twittermask
{
height:38px;
width:38px;
position:absolute;
top:0;
left:0;
z-index:10;
background-image:url('../img/twittermask.png');
background-position:top left;
background-repeat:no-repeat;
.facebookmask {
background-image:url('../img/facebookmask.png');
background-position:top left;
background-repeat:no-repeat;
height:38px;
left:0;
position:absolute;
top:0;
width:38px;
z-index:10;
}
.twitter {
background-image:url('../img/twitter.png');
}
.twittermask {
background-image:url('../img/twittermask.png');
background-position:top left;
background-repeat:no-repeat;
height:38px;
left:0;
position:absolute;
top:0;
width:38px;
z-index:10;
}
/*************************FOOTER****************************/
/* Footer */
footer {
position:fixed;
background-image:url('../img/fondfooter.png');
background-position:top left;
background-repeat:repeat-x;
bottom:0;
color:#adadad;
padding-top:20px;
position:fixed;
text-align:center;
width:100%;
background-image:url('../img/fondfooter.png');
background-repeat:repeat-x;
background-position:top left;
padding-top:20px;
color:#adadad;
font-size:12px;
z-index:11;
}
@ -123,17 +122,16 @@ footer {
footer a{
color:#adadad;
text-decoration:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
text-decoration:none;
}
footer a:focus,
footer a:hover
{
footer a:hover {
color:#f2084a;
outline:none;
color:#f2084a;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
@ -144,49 +142,44 @@ color:#f2084a;
/*************************CONTENT ACCUEIL****************************/
/* Home content */
.logo {
padding-bottom:55px;
}
.labelurl
{
position:relative;
color:#3f3f3f;
font-size:19px;
.labelurl {
color:#3f3f3f;
font-size:19px;
position:relative;
}
.champs
{
position:relative;
margin-bottom:70px;
margin-top:8px;
.champs {
margin-bottom:70px;
margin-top:8px;
position:relative;
}
.downloadBtn {
position:relative;
background-color:#3A3A3A;
border: 3px solid #a5a5a5;
color:#dedede;
border-radius:10px;
padding: 12px 14px;
font-size:24px;
font-weight:800;
color:#dedede;
cursor:pointer;
display:inline-block;
font-weight:800;
padding: 12px 14px;
position:relative;
text-decoration:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
text-decoration:none;
display:inline-block;
}
.downloadBtn:focus,
.downloadBtn:hover
{
outline:none;
.downloadBtn:hover {
background-color:#f2084a;
outline:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
@ -197,22 +190,21 @@ margin-top:8px;
}
.URLinput{
position:relative;
background-color:#fff;
border: 3px solid #a5a5a5;
color:#3F3F3F;
border-radius:10px;
padding: 12px 12px 12px 12px;
min-width:426px;
font-size:24px;
color:#3F3F3F;
font-weight:800;
margin-right:8px;
min-width:426px;
padding: 12px 12px 12px 12px;
position:relative;
}
.URLinput:focus {
outline: none;
border-color:#3A3A3A;
outline: none;
}
.URLinput:-webkit-input-placeholder{
@ -223,176 +215,163 @@ margin-top:8px;
}
.combatiblelink {
position:relative;
color:#a5a5a5;
font-size:13px;
z-index:10;
text-decoration:none;
background-image:url('../img/compatiblerouage.png');
background-position:0 100%;
background-repeat:no-repeat;
padding-left:41px;
padding-top:10px;
padding-bottom:10px;
background-image:url('../img/compatiblerouage.png');
background-position:0 100%;
background-repeat:no-repeat;
color:#a5a5a5;
padding-bottom:10px;
padding-left:41px;
padding-top:10px;
position:relative;
text-decoration:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
z-index:10;
}
.combatiblelink:focus,
.combatiblelink:hover
{
.combatiblelink:hover {
background-position:0 0;
color:#f2084a;
outline:none;
background-position:0 0;
color:#f2084a;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
}
#bookmarklet{
padding:15px;
}
.bookmarklet{
position:relative;
font-size:13px;
color:gray;
z-index:10;
text-decoration:none;
padding-left:30px;
padding-right:30px;
padding-top:10px;
padding-bottom:10px;
border: 2px dotted;
}
.mp3
{
.bookmarklet {
border: 2px dotted;
color:gray;
padding:10px 30px;
position:relative;
text-decoration:none;
z-index:10;
}
.mp3 {
background-color:#cecece;
color:#696969;
border-radius:6px;
width:622px;
font-size:14px;
color:#696969;
height:26px;
margin-top:12px;
position:relative;
text-align:left;
font-weight:300;
width:622px;
}
.mp3 p
{
padding:3px;
.mp3 p {
padding:3px;
}
/*
Demo CSS code
*/
.audio:not(:checked),
.audio:checked {
position: absolute;
.audio:not(:checked),
.audio:checked {
left: -9999px;
}
.audio:not(:checked) + label,
.audio:checked + label {
position: relative;
padding-left: 82px;
position: absolute;
}
.audio:not(:checked) + label,
.audio:checked + label {
cursor: pointer;
line-height:22px;
}
.audio:not(:checked) + label:before,
.audio:checked + label:before,
.audio:not(:checked) + label:after,
.audio:checked + label:after {
padding-left: 82px;
position: relative;
}
.audio:not(:checked) + label:before,
.audio:checked + label:before,
.audio:not(:checked) + label:after,
.audio:checked + label:after {
content: '';
position: absolute;
}
.audio:not(:checked) + label:before,
.audio:checked + label:before {
left:0; top: -1px;
width: 45px; height: 20px;
}
.audio:not(:checked) + label:before,
.audio:checked + label:before {
background: #ffffff;
border-radius: 6px;
height: 20px;
left:0;
top: -1px;
-webkit-transition: background-color .2s;
-moz-transition: background-color .2s;
-ms-transition: background-color .2s;
-o-transition: background-color .2s;
transition: background-color .2s;
}
.audio:not(:checked) + label:after,
.audio:checked + label:after {
width: 16px; height: 16px;
width: 45px;
}
.audio:not(:checked) + label:after,
.audio:checked + label:after {
background: #3a3a3a;
border-radius: 6px;
height: 16px;
left: 2px;
top: 1px;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
border-radius: 6px;
background: #3a3a3a;
top: 1px; left: 2px;
}
width: 16px;
}
.audio:focus + label {
.audio:focus + label {
color:black;
}
}
/* on checked */
.audio:checked + label:before {
/* on checked */
.audio:checked + label:before {
background:#f2084a;
}
.audio:checked + label:after {
}
.audio:checked + label:after {
background: #fff;
top: 1px; left: 27px;
}
left: 27px;
top: 1px;
}
.audio:checked + label .ui,
.audio:not(:checked) + label .ui:before,
.audio:checked + label .ui:after {
position: absolute;
left: 3px;
width: 45px;
.audio:checked + label .ui,
.audio:not(:checked) + label .ui:before,
.audio:checked + label .ui:after {
border-radius: 15px;
font-size: 11px;
font-weight: bold;
line-height: 17px;
height:20px;
left: 3px;
line-height: 17px;
position: absolute;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
}
.audio:not(:checked) + label .ui:before {
width: 45px;
}
.audio:not(:checked) + label .ui:before {
background-image:url('../img/mp3hover.png');
background-position:right top;
background-repeat:no-repeat;
content: "no";
left: 0;
min-width:56px;
padding-left:23px;
padding-top:2px;
background-image:url('../img/mp3hover.png');
background-repeat:no-repeat;
background-position:right top;
width:56px;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
}
.audio:checked + label .ui:after {
content: "yes";
color: #fff;
}
.audio:checked + label .ui:after {
background-image:url('../img/mp3.png');
background-repeat:no-repeat;
background-position:right top;
width:73px;
background-repeat:no-repeat;
color: #fff;
content: "yes";
-webkit-transition: all .2s;
-moz-transition: all .2s;
-ms-transition: all .2s;
-o-transition: all .2s;
transition: all .2s;
}
width:73px;
}
@ -410,120 +389,114 @@ padding:3px;
width: 600px;
}
.playlist-entry h3 {
.playlist-entry-title {
margin-top: 0;
}
.playlist-entry h3 a {
.playlist-entry-title a {
text-decoration: none;
}
.playlist-entry h3 a:hover {
.playlist-entry-title a:hover {
text-decoration: underline;
}
.playlist-entry .downloadBtn {
font-size: 16px;
border-width: 2px;
font-size: 16px;
}
/*************************CONTENT COMPATIBLES****************************/
/* Supported websites list */
.logobis
{
width:447px;
height:107px;
position:relative;
margin:0 auto 10px auto;
.logobis {
height:107px;
margin:0 auto 10px auto;
position:relative;
width:447px;
}
.logocompatible
{
width:447px;
height:107px;
background-image:url('../img/logocompatible.png');
background-repeat:repeat-y;
background-position:0 0;
.logocompatible {
background-image:url('../img/logocompatible.png');
background-position:0 0;
background-repeat:repeat-y;
display:block;
height:107px;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;
display:block;
width:447px;
}
.logocompatible:focus,
.logocompatible:hover {
outline:none;
background-position:0 100%;
outline:none;
-webkit-transition: all 0.1s ease-in;
-moz-transition: all 0.1s ease-in;
-o-transition: all 0.1s ease-in;}
.logocompatiblemask
{
z-index:10;
position:absolute;
top:0;
left:0;
width:447px;
height:107px;
background-image:url('../img/logocompatiblemask.png');
background-position:0 100%;
background-repeat:no-repeat;
}
.titre
{
font-family: 'Open Sans', sans-serif;
font-weight:300;
color:#383838;
font-size:48px;
}
.tripleliste
{
margin-top:80px;
width:800px;
position:relative;
margin-left:auto;
margin-right:auto;
-o-transition: all 0.1s ease-in;
}
.tripleliste ul
{
margin-bottom:1em;
width:600px;
margin-left:120px;}
.logocompatiblemask {
background-image:url('../img/logocompatiblemask.png');
background-position:0 100%;
background-repeat:no-repeat;
height:107px;
left:0;
position:absolute;
top:0;
width:447px;
z-index:10;
}
.tripleliste ul li
{text-align:left;
List-Style-Type:none;
color:#383838;
font-size:16px;
.titre {
color:#383838;
font-family: 'Open Sans', sans-serif;
font-size:48px;
font-weight:300;
}
width:200px;
float:left;
position:relative;
.tripleliste {
margin-left:auto;
margin-right:auto;
margin-top:80px;
position:relative;
width:800px;
}
.tripleliste ul {
margin-bottom:1em;
margin-left:120px;
width:600px;
}
.tripleliste ul li {
color:#383838;
float:left;
list-style-type:none;
position:relative;
text-align:left;
width:200px;
}
html,
body {
margin:0;
height:100%;
margin:0;
}
.wrapper {
height:100%;
display:table;
margin:auto;
padding-bottom:110px;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
display:table;
height:100%;
margin:auto;
padding-bottom:110px;
}
.main {
display:table-cell;
@ -595,6 +568,65 @@ h1 {
font-family:monospace;
}
.locales {
float: left;
padding-left: 1em;
padding-top: 1em;
text-align: left;
}
.locales a,
.locales a:visited {
color: #696969;
text-decoration: none;
}
.supportedLocales {
background-color: #fff;
list-style-type: none;
margin: 0;
opacity: 0;
padding-left: 0;
transition: visibility 0.5s;
visibility: hidden;
}
.supportedLocales li {
border-bottom: thin solid #E1E1E1;
}
.supportedLocales li:last-child {
border-bottom: none;
}
.supportedLocales li a {
display: block;
padding: 1em;
padding-right: 2em;
}
.supportedLocales li:hover {
background-color: #cecece;
}
.localesBtn {
background-color: transparent;
border: none;
cursor: pointer;
display: inline-block;
padding: 1em;
}
.localesBtn:focus {
background-color: #fff;
pointer-events: none;
}
.localesBtn:focus + .supportedLocales {
opacity: 1;
visibility: visible;
}
@media (max-width: 640px) {
.formats,
.thumb {
@ -619,9 +651,9 @@ h1 {
.champs,
.URLinput,
.mp3 {
width:90%;
margin:auto;
height:auto;
margin:auto;
width:90%;
}
.logo {
@ -629,8 +661,8 @@ h1 {
}
.logocompatible img {
width:100%;
height: auto;
width:100%;
}
.downloadBtn {
@ -646,9 +678,9 @@ h1 {
.tripleliste ul,
.tripleliste {
width:auto;
margin-left:auto;
margin-top:auto;
width:auto;
}
.logocompatiblemask {
@ -656,9 +688,9 @@ h1 {
}
.logocompatible {
height:auto;
background-image:none;
background-color:#4F4F4F;
background-image:none;
height:auto;
}
.logocompatiblemask,

View file

@ -0,0 +1,134 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Pierre Rudloff <contact@rudloff.pro>\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.6.10\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: templates/error.tpl:5
msgid "An error occured"
msgstr "Une erreur est survenue"
#: templates/error.tpl:6
msgid "Please check the URL of your video."
msgstr "Veuillez vérifier l'URL de votre vidéo"
#: templates/playlist.tpl:5
msgid "Videos extracted from"
msgstr "Vidéos extraites depuis"
#: templates/playlist.tpl:7
msgid ":"
msgstr " :"
#: templates/playlist.tpl:26 templates/password.tpl:10 templates/video.tpl:85
#: templates/video.tpl:88 templates/index.tpl:18
msgid "Download"
msgstr "Télécharger"
#: templates/playlist.tpl:27
msgid "More options"
msgstr "Plus d'options"
#: templates/password.tpl:5
msgid "This video is protected"
msgstr "Cette vidéo est protégée"
#: templates/password.tpl:6
msgid "You need a password in order to download this video."
msgstr "L'accès à cette vidéo nécessite un mot de passe."
#: templates/password.tpl:8
msgid "Video password"
msgstr "Mot de passe de la vidéo"
#: templates/extractors.tpl:4
msgid "Supported websites"
msgstr "Sites web supportés"
#: templates/video.tpl:6
msgid "You are going to download"
msgstr "Vous allez télécharger"
#: templates/video.tpl:26
msgid "Available formats:"
msgstr "Formats disponibles :"
#: templates/video.tpl:31
msgid "Generic formats"
msgstr "Formats génériques"
#: templates/video.tpl:34
msgid "Best"
msgstr "Meilleure qualité"
#: templates/video.tpl:39
msgid "Remux best video with best audio"
msgstr "Combiner la meilleure vidéo avec le meilleur audio"
#: templates/video.tpl:43
msgid "Worst"
msgstr "Pire qualité"
#: templates/video.tpl:46
msgid "Detailed formats"
msgstr "Formats détaillés"
#: templates/inc/footer.tpl:4
msgid "Code by"
msgstr "Développé par"
#: templates/inc/footer.tpl:6
msgid "Design by"
msgstr "Designé par"
#: templates/inc/footer.tpl:12
msgid "AllTube Download on Facebook"
msgstr "AllTube Download sur Facebook"
#: templates/inc/footer.tpl:12
msgid "Like us on Facebook"
msgstr "Suivez-nous sur Facebook"
#: templates/inc/footer.tpl:14
msgid "Get the code"
msgstr "Obtenir le code"
#: templates/inc/footer.tpl:16
msgid "Based on"
msgstr "Basé sur"
#: templates/inc/header.tpl:4
msgid "Share on Twitter"
msgstr "Partager sur Twitter"
#: templates/inc/header.tpl:5
msgid "Share on Facebook"
msgstr "Partager sur Facebook"
#: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)"
msgstr "Copiez ici l'URL de votre vidéo (Youtube, Dailymotion, etc.)"
#: templates/index.tpl:23
msgid "Audio only (MP3)"
msgstr "Audio uniquement (MP3)"
#: templates/index.tpl:28
msgid "See all supported websites"
msgstr "Voir tous les sites supportés"
#: templates/index.tpl:30
msgid "Drag this to your bookmarks bar:"
msgstr "Glissez ce lien dans votre barre de favoris :"
#: templates/index.tpl:31
msgid "Bookmarklet"
msgstr "Bookmarklet"

123
i18n/template.pot Normal file
View file

@ -0,0 +1,123 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
#: templates/error.tpl:5
msgid "An error occured"
msgstr ""
#: templates/error.tpl:6
msgid "Please check the URL of your video."
msgstr ""
#: templates/playlist.tpl:5
msgid "Videos extracted from"
msgstr ""
#: templates/playlist.tpl:7
msgid ":"
msgstr ""
#: templates/playlist.tpl:26 templates/password.tpl:10 templates/video.tpl:85
#: templates/video.tpl:88 templates/index.tpl:18
msgid "Download"
msgstr ""
#: templates/playlist.tpl:27
msgid "More options"
msgstr ""
#: templates/password.tpl:5
msgid "This video is protected"
msgstr ""
#: templates/password.tpl:6
msgid "You need a password in order to download this video."
msgstr ""
#: templates/password.tpl:8
msgid "Video password"
msgstr ""
#: templates/extractors.tpl:4
msgid "Supported websites"
msgstr ""
#: templates/video.tpl:6
msgid "You are going to download"
msgstr ""
#: templates/video.tpl:26
msgid "Available formats:"
msgstr ""
#: templates/video.tpl:31
msgid "Generic formats"
msgstr ""
#: templates/video.tpl:34
msgid "Best"
msgstr ""
#: templates/video.tpl:39
msgid "Remux best video with best audio"
msgstr ""
#: templates/video.tpl:43
msgid "Worst"
msgstr ""
#: templates/video.tpl:46
msgid "Detailed formats"
msgstr ""
#: templates/inc/footer.tpl:4
msgid "Code by"
msgstr ""
#: templates/inc/footer.tpl:6
msgid "Design by"
msgstr ""
#: templates/inc/footer.tpl:12
msgid "AllTube Download on Facebook"
msgstr ""
#: templates/inc/footer.tpl:12
msgid "Like us on Facebook"
msgstr ""
#: templates/inc/footer.tpl:14
msgid "Get the code"
msgstr ""
#: templates/inc/footer.tpl:16
msgid "Based on"
msgstr ""
#: templates/inc/header.tpl:4
msgid "Share on Twitter"
msgstr ""
#: templates/inc/header.tpl:5
msgid "Share on Facebook"
msgstr ""
#: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)"
msgstr ""
#: templates/index.tpl:23
msgid "Audio only (MP3)"
msgstr ""
#: templates/index.tpl:28
msgid "See all supported websites"
msgstr ""
#: templates/index.tpl:30
msgid "Drag this to your bookmarks bar:"
msgstr ""
#: templates/index.tpl:31
msgid "Bookmarklet"
msgstr ""

View file

@ -0,0 +1,135 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Shiqiang Yu <tony19955569@gmail.com>\n"
"Language-Team: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: ..\..\..\templates/error.tpl:5
msgid "An error occured"
msgstr "出错了"
#: ..\..\..\templates/error.tpl:6
msgid "Please check the URL of your video."
msgstr "请检查您的视频的 URL。"
#: ..\..\..\templates/extractors.tpl:4
msgid "Supported websites"
msgstr "支持的网站"
#: ..\..\..\templates/inc/footer.tpl:4
msgid "Code by"
msgstr "代码来自"
#: ..\..\..\templates/inc/footer.tpl:6
msgid "Design by"
msgstr "设计来自"
#: ..\..\..\templates/inc/footer.tpl:12
msgid "AllTube Download on Facebook"
msgstr "去Alltube Download的Facebook页面"
#: ..\..\..\templates/inc/footer.tpl:12
msgid "Like us on Facebook"
msgstr "在Facebook关注我们"
#: ..\..\..\templates/inc/footer.tpl:14
msgid "Get the code"
msgstr "获取代码"
#: ..\..\..\templates/inc/footer.tpl:16
msgid "Based on"
msgstr "基于"
#: ..\..\..\templates/inc/header.tpl:4
msgid "Share on Twitter"
msgstr "分享到 Twitter"
#: ..\..\..\templates/inc/header.tpl:5
msgid "Share on Facebook"
msgstr "分享到 Facebook"
#: ..\..\..\templates/index.tpl:9
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)"
msgstr "在这里复制您的视频 Youtube、 Dailymotion 等) 的 URL"
#: ..\..\..\templates/index.tpl:19 ..\..\..\templates/password.tpl:10
#: ..\..\..\templates/playlist.tpl:23 ..\..\..\templates/video.tpl:85
#: ..\..\..\templates/video.tpl:90
msgid "Download"
msgstr "下载"
#: ..\..\..\templates/index.tpl:24
msgid "Audio only (MP3)"
msgstr "仅限音频mp3"
#: ..\..\..\templates/index.tpl:29
msgid "See all supported websites"
msgstr "请参阅支持的所有网站"
#: ..\..\..\templates/index.tpl:31
msgid "Drag this to your bookmarks bar:"
msgstr "把这个拖到你的书签:"
#: ..\..\..\templates/index.tpl:32
msgid "Bookmarklet"
msgstr "书签工具"
#: ..\..\..\templates/password.tpl:5
msgid "This video is protected"
msgstr "这个视频受保护"
#: ..\..\..\templates/password.tpl:6
msgid "You need a password in order to download this video."
msgstr "你需要密码才能下载这个视频。"
#: ..\..\..\templates/password.tpl:8
msgid "Video password"
msgstr "视频密码"
#: ..\..\..\templates/playlist.tpl:5
msgid "Videos extracted from the"
msgstr "视频从"
#: ..\..\..\templates/playlist.tpl:7
msgid "playlist:"
msgstr "播放列表提取:"
#: ..\..\..\templates/playlist.tpl:24
msgid "More options"
msgstr "更多选项"
#: ..\..\..\templates/video.tpl:6
msgid "You are going to download"
msgstr "你即将下载"
#: ..\..\..\templates/video.tpl:24
msgid "Available formats:"
msgstr "可用的格式︰"
#: ..\..\..\templates/video.tpl:31
msgid "Generic formats"
msgstr "通用格式"
#: ..\..\..\templates/video.tpl:34
msgid "Best"
msgstr "最佳"
#: ..\..\..\templates/video.tpl:39
msgid "Remux best video with best audio"
msgstr "重新封装最佳视频与最佳音频"
#: ..\..\..\templates/video.tpl:43
msgid "Worst"
msgstr "最差"
#: ..\..\..\templates/video.tpl:46
msgid "Detailed formats"
msgstr "详细格式"

View file

@ -3,6 +3,9 @@
require_once __DIR__.'/vendor/autoload.php';
use Alltube\Config;
use Alltube\Controller\FrontController;
use Alltube\LocaleManager;
use Alltube\LocaleMiddleware;
use Alltube\PlaylistArchiveStream;
use Alltube\UglyRouter;
use Alltube\ViewFactory;
use Slim\App;
@ -12,6 +15,8 @@ if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/index.ph
die;
}
stream_wrapper_register('playlist', PlaylistArchiveStream::class);
$app = new App();
$container = $app->getContainer();
$config = Config::getInstance();
@ -19,6 +24,8 @@ if ($config->uglyUrls) {
$container['router'] = new UglyRouter();
}
$container['view'] = ViewFactory::create($container);
$container['locale'] = new LocaleManager($_COOKIE);
$app->add(new LocaleMiddleware($container));
$controller = new FrontController($container, null, $_COOKIE);
@ -40,4 +47,8 @@ $app->get(
'/redirect',
[$controller, 'redirect']
)->setName('redirect');
$app->get(
'/locale/{locale}',
[$controller, 'locale']
)->setName('locale');
$app->run();

View file

@ -33,10 +33,10 @@ var castModule = (function () {
function sessionListener(e) {
session = e;
session.addMediaListener(onMediaDiscovered.bind(this, 'addMediaListener'));
session.addUpdateListener(updateListener.bind(this));
session.addMediaListener(onMediaDiscovered);
session.addUpdateListener(updateListener);
if (session.media.length !== 0) {
onMediaDiscovered('onRequestSessionSuccess', session.media[0]);
onMediaDiscovered();
}
}
@ -52,7 +52,7 @@ var castModule = (function () {
function onRequestSessionSuccess(e) {
session = e;
var videoLink = document.getElementById('video_link'), videoURL = videoLink.dataset.video, mediaInfo = new chrome.cast.media.MediaInfo(videoURL, 'video/' + videoLink.dataset.ext), request = new chrome.cast.media.LoadRequest(mediaInfo);
session.loadMedia(request, onMediaDiscovered.bind(this, 'loadMedia'), onMediaError);
session.loadMedia(request, onMediaDiscovered, onMediaError);
}
function onLaunchError(e) {
@ -80,7 +80,8 @@ var castModule = (function () {
}
function initializeCastApi() {
var sessionRequest = new chrome.cast.SessionRequest(chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID), apiConfig = new chrome.cast.ApiConfig(sessionRequest, sessionListener, receiverListener, chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED);
var sessionRequest = new chrome.cast.SessionRequest(chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID),
apiConfig = new chrome.cast.ApiConfig(sessionRequest, sessionListener, receiverListener, chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED);
chrome.cast.initialize(apiConfig, onInitSuccess, onError);
}
@ -103,4 +104,6 @@ var castModule = (function () {
};
}());
window.addEventListener('load', castModule.init, false);
if (typeof window === 'object') {
window.addEventListener('load', castModule.init, false);
}

1603
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,16 @@
{
"name": "alltube",
"description": "HTML GUI for youtube-dl",
"version": "0.9.0",
"version": "0.10.0",
"author": "Pierre Rudloff",
"bugs": "https://github.com/Rudloff/alltube/issues",
"dependencies": {
"bower": "~1.8.0",
"grunt": "~1.0.1",
"grunt-contrib-cssmin": "~2.0.0",
"grunt-contrib-uglify": "~2.2.0"
"grunt-contrib-csslint": "~2.0.0",
"grunt-contrib-cssmin": "~2.2.0",
"grunt-contrib-uglify": "~3.0.0",
"grunt-potomo": "~3.5.0"
},
"devDependencies": {
"grunt-contrib-compress": "~1.4.1",

View file

@ -9,7 +9,7 @@ You can ususally download the video by doing *File > Save to* or *ctrl + S*.
## How do I change config parameters?
You need to create a YAML file called `config.yml` at the root of your project.
You need to create a YAML file called `config.yml` in the `config/` folder.
Here are the parameters that you can set:
* `youtubedl`: path to your youtube-dl binary

View file

@ -1 +1 @@
Sitemap: http://alltubedownload.net/sitemap.xml
Sitemap: http://alltubedownload.net/resources/sitemap.xml

View file

@ -2,8 +2,8 @@
<div class="wrapper">
<div class="main error">
{include file="inc/logo.tpl"}
<h2>An error occured</h2>
Please check the URL of your video.
<h2>{t}An error occured{/t}</h2>
{t}Please check the URL of your video.{/t}
<p><i>
{foreach $errors as $error}
{$error|escape}

View file

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

View file

@ -1,19 +1,19 @@
</div>
<footer>
<footer class="small-font">
<div class="footer_wrapper">
Code by <a rel="author" target="blank"
{t}Code by{/t} <a rel="author" target="blank"
href="http://rudloff.pro/">Pierre Rudloff</a>
&middot; Design by
&middot; {t}Design by{/t}
<a rel="author" target="blank"
href="http://olivierhaquette.fr">Olivier Haquette</a>
&middot;
<a target="_blank"
href="https://www.facebook.com/pages/AllTube-Download/571380966249415"
title="AllTube Download on Facebook">Like us on Facebook</a>
title="{t}AllTube Download on Facebook{/t}">{t}Like us on Facebook{/t}</a>
&middot;
<a href="https://github.com/Rudloff/alltube">Get the code</a>
<a href="https://github.com/Rudloff/alltube">{t}Get the code{/t}</a>
&middot;
Based on <a href="http://rg3.github.io/youtube-dl/">youtube-dl</a>
{t}Based on{/t} <a href="http://rg3.github.io/youtube-dl/">youtube-dl</a>
</div>
</footer>
<script src="{base_url}/dist/main.js"></script>

View file

@ -1,5 +1,6 @@
{locale path="../i18n" domain="Alltube"}
<!Doctype HTML>
<html lang="en">
<html {if isset($locale)}lang="{$locale->getBcp47()}"{/if}>
<head>
<meta charset="UTF-8" />
<meta name=viewport content="width=device-width, initial-scale=1">
@ -20,6 +21,6 @@
<meta name="twitter:creator" content="@Tael67" />
<script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script>
<meta name="theme-color" content="#4F4F4F">
<link rel="manifest" href="manifest.json" />
<link rel="manifest" href="{base_url}/resources/manifest.json" />
</head>
<body class="{$class}">

View file

@ -1,8 +1,26 @@
<header>
{if isset($supportedLocales)}
<div class="locales small-font">
<button class="localesBtn small-font" title="{t}Switch language{/t}">
{if isset($locale)}
{$locale->getCountry()->getEmoji()}
{else}
Set language
{/if}
</button>
<ul class="supportedLocales">
{foreach $supportedLocales as $supportedLocale}
{if $supportedLocale != $locale}
<li><a hreflang="{$supportedLocale->getBcp47()}" lang="{$supportedLocale->getBcp47()}" href="{path_for name='locale' data=['locale'=>$supportedLocale->getIso15897()]}">{$supportedLocale->getCountry()->getEmoji()} {$supportedLocale->getFullName()}</a></li>
{/if}
{/foreach}
</ul>
</div>
{/if}
<div class="social">
<a class="twitter" href="http://twitter.com/home?status={base_url|urlencode}" target="_blank">
Share on Twitter<div class="twittermask"></div></a>
<a class="facebook" href="https://www.facebook.com/sharer/sharer.php?u={base_url|urlencode}" target="_blank">Share on Facebook<div class="facebookmask"></div></a>
<a class="twitter" href="http://twitter.com/home?status={base_url|urlencode}" title="{t}Share on Twitter{/t}" target="_blank">
<div class="twittermask"></div></a>
<a class="facebook" href="https://www.facebook.com/sharer/sharer.php?u={base_url|urlencode}" title="{t}Share on Facebook{/t}" target="_blank"><div class="facebookmask"></div></a>
</div>
</header>
<div class="wrapper">

View file

@ -5,30 +5,30 @@
alt="AllTube Download" width="328" height="284"></div>
<form action="{path_for name="video"}">
<label class="labelurl" for="url">
Copy here the URL of your video (Youtube, Dailymotion, etc.)
{t}Copy here the URL of your video (Youtube, Dailymotion, etc.){/t}
</label>
<div class="champs">
<span class="URLinput_wrapper">
<input class="URLinput" type="url" name="url" id="url"
<input class="URLinput large-font" type="url" name="url" id="url"
required autofocus placeholder="http://example.com/video" />
</span>
{if $uglyUrls}
{if $config->uglyUrls}
<input type="hidden" name="page" value="video" />
{/if}
<input class="downloadBtn" type="submit" value="Download" /><br/>
{if $convert}
<div class="mp3">
<input class="downloadBtn large-font" type="submit" value="{t}Download{/t}" /><br/>
{if $config->convert}
<div class="mp3 small-font">
<p><input type="checkbox" id="audio" class="audio" name="audio">
<label for="audio"><span class="ui"></span>
Audio only (MP3)</label></p>
{t}Audio only (MP3){/t}</label></p>
</div>
{/if}
</div>
</form>
<a class="combatiblelink" href="{path_for name="extractors"}">See all supported websites</a>
<a class="combatiblelink small-font" href="{path_for name="extractors"}">{t}See all supported websites{/t}</a>
<div id="bookmarklet" class="bookmarklet_wrapper">
<p> Drag this to your bookmarks bar: </p>
<a class="bookmarklet" href="javascript:window.location='{$domain}{path_for name='video'}?url='+encodeURIComponent(location.href);">Bookmarklet</a>
<p> {t}Drag this to your bookmarks bar:{/t} </p>
<a class="bookmarklet small-font" href="javascript:window.location='{$domain}{path_for name='video'}?url='+encodeURIComponent(location.href);">{t}Bookmarklet{/t}</a>
</div>
</div>

View file

@ -2,12 +2,12 @@
<div class="wrapper">
<div class="main">
{include file="inc/logo.tpl"}
<h2>This video is protected</h2>
<p>You need a password in order to download this video.</p>
<h2>{t}This video is protected{/t}</h2>
<p>{t}You need a password in order to download this video.{/t}</p>
<form action="" method="POST">
<input class="URLinput" type="password" name="password" title="Video password" />
<input class="URLinput" type="password" name="password" title="{t}Video password{/t}" />
<br/><br/>
<input class="downloadBtn" type="submit" value="Download" />
<input class="downloadBtn" type="submit" value="{t}Download{/t}" />
</form>
</div>
{include file='inc/footer.tpl'}

View file

@ -2,13 +2,16 @@
<div class="wrapper">
<div class="main">
{include file="inc/logo.tpl"}
<p>Videos extracted from the {if isset($video->title)}<i>
<p>{t}Videos extracted from{/t} {if isset($video->title)}<i>
<a href="{$video->webpage_url}">
{$video->title}</a></i>{/if} playlist:
{$video->title}</a></i>{/if}{t}:{/t}
</p>
{if $config->stream}
<a href="{path_for name="redirect"}?url={$video->webpage_url}" class="downloadBtn">Download everything</a>
{/if}
{foreach $video->entries as $video}
<div class="playlist-entry">
<h3><a target="_blank" href="{strip}
<h3 class="playlist-entry-title"><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}
@ -20,8 +23,8 @@
{$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>
<a target="_blank" class="downloadBtn" href="{path_for name="redirect"}?url={$video->url}">{t}Download{/t}</a>
<a target="_blank" href="{path_for name="video"}?url={$video->url}">{t}More options{/t}</a>
</div>
{/foreach}

View file

@ -3,7 +3,7 @@
<div itemscope itemtype="http://schema.org/VideoObject">
<div class="main">
{include file="inc/logo.tpl"}
<p id="download_intro">You are going to download<i itemprop="name">
<p id="download_intro">{t}You are going to download{/t}<i itemprop="name">
<a itemprop="url" id="video_link"
data-ext="{$video->ext}"
data-video="{$video->url|escape}"
@ -20,30 +20,30 @@
<meta itemprop="uploadDate" content="{$video->upload_date}" />
{/if}
<br/>
{if isset($video->formats)}
<h3><label for="format">Available formats:</label></h3>
<form action="{path_for name="redirect"}">
<form action="{path_for name="redirect"}">
<input type="hidden" name="url" value="{$video->webpage_url}" />
{if $uglyUrls}
{if isset($video->formats)}
<h3><label for="format">{t}Available formats:{/t}</label></h3>
{if $config->uglyUrls}
<input type="hidden" name="page" value="redirect" />
{/if}
<select name="format" id="format" class="formats monospace">
<optgroup label="Generic formats">
<optgroup label="{t}Generic formats{/t}">
<option value="best{$protocol}">
{strip}
Best ({$video->ext})
{t}Best{/t} ({$video->ext})
{/strip}
</option>
{if $remux}
{if $config->remux}
<option value="bestvideo+bestaudio">
Remux best video with best audio
{t}Remux best video with best audio{/t}
</option>
{/if}
<option value="worst{$protocol}">
Worst
{t}Worst{/t}
</option>
</optgroup>
<optgroup label="Detailed formats" class="monospace">
<optgroup label="{t}Detailed formats{/t}" class="monospace">
{foreach $video->formats as $format}
{if $config->stream || $format->protocol|in_array:array('http', 'https')}
{strip}
@ -82,12 +82,10 @@
{/foreach}
</optgroup>
</select><br/><br/>
<input class="downloadBtn" type="submit" value="Download" /><br/>
<input class="downloadBtn" type="submit" value="{t}Download{/t}" /><br/>
</form>
{else}
<input type="hidden" name="format" value="best{$protocol}" />
<a class="downloadBtn"
href="{$video->url|escape}">Download</a><br/>
<input class="downloadBtn" type="submit" value="{t}Download{/t}" /><br/>
{/if}
</div>
</div>

View file

@ -24,7 +24,7 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
*/
protected function setUp()
{
$this->config = Config::getInstance('config_test.yml');
$this->config = Config::getInstance('config/config_test.yml');
}
/**
@ -83,7 +83,7 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
Config::destroyInstance();
putenv('CONVERT=1');
putenv('PYTHON=foo');
$config = Config::getInstance('config_test.yml');
$config = Config::getInstance('config/config_test.yml');
$this->assertEquals($config->convert, true);
$this->assertEquals($config->python, 'foo');
putenv('CONVERT');

View file

@ -7,6 +7,7 @@ namespace Alltube\Test;
use Alltube\Config;
use Alltube\Controller\FrontController;
use Alltube\LocaleManager;
use Alltube\ViewFactory;
use Slim\Container;
use Slim\Http\Environment;
@ -55,7 +56,8 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
$this->request = Request::createFromEnvironment(Environment::mock());
$this->response = new Response();
$this->container['view'] = ViewFactory::create($this->container, $this->request);
$this->controller = new FrontController($this->container, Config::getInstance('config_test.yml'));
$this->container['locale'] = new LocaleManager();
$this->controller = new FrontController($this->container, Config::getInstance('config/config_test.yml'));
$this->container['router']->map(['GET'], '/', [$this->controller, 'index'])
->setName('index');
$this->container['router']->map(['GET'], '/video', [$this->controller, 'video'])
@ -64,6 +66,8 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
->setName('extractors');
$this->container['router']->map(['GET'], '/redirect', [$this->controller, 'redirect'])
->setName('redirect');
$this->container['router']->map(['GET'], '/locale', [$this->controller, 'locale'])
->setName('locale');
}
/**
@ -396,7 +400,7 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
{
$this->assertRequestIsOk(
'redirect',
['url'=> 'http://www.rtl2.de/sendung/grip-das-motormagazin/folge/folge-203-0'],
['url'=> 'http://www.canalc2.tv/video/12163', 'format'=>'rtmp'],
new Config(['stream'=>true])
);
}
@ -453,4 +457,48 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
{
$this->assertRequestIsServerError('redirect', ['url'=>'http://example.com/foo']);
}
/**
* Test the redirect() function with an video that returns an empty URL.
* This can be caused by trying to redirect to a playlist.
*
* @return void
*/
public function testRedirectWithEmptyUrl()
{
$this->assertRequestIsServerError(
'redirect',
['url'=> 'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC']
);
}
/**
* Test the redirect() function with a playlist stream.
*
* @return void
*/
public function testRedirectWithPlaylist()
{
$this->assertRequestIsOk(
'redirect',
['url'=> 'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC'],
new Config(['stream'=>true])
);
}
/**
* Test the locale() function.
*
* @return void
*/
public function testLocale()
{
$this->assertTrue(
$this->controller->locale(
$this->request,
$this->response,
['locale'=> 'fr_FR']
)->isRedirect()
);
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* LocaleManagerTest class.
*/
namespace Alltube\Test;
use Alltube\Locale;
use Alltube\LocaleManager;
/**
* Unit tests for the Config class.
*/
class LocaleManagerTest extends \PHPUnit_Framework_TestCase
{
/**
* LocaleManager class instance.
*
* @var LocaleManager
*/
private $localeManager;
/**
* Prepare tests.
*/
protected function setUp()
{
$this->localeManager = new LocaleManager();
}
/**
* Test the getSupportedLocales function.
*
* @return void
*/
public function testConstructorWithCookies()
{
$_SESSION['Alltube\LocaleManager']['locale'] = 'foo_BAR';
$localeManager = new LocaleManager([]);
$this->assertEquals('foo_BAR', (string) $localeManager->getLocale());
}
/**
* Test the getSupportedLocales function.
*
* @return void
*/
public function testGetSupportedLocales()
{
foreach ($this->localeManager->getSupportedLocales() as $locale) {
$this->assertInstanceOf(Locale::class, $locale);
}
}
/**
* Test the getLocale function.
*
* @return void
*/
public function testGetLocale()
{
$this->assertNull($this->localeManager->getLocale());
}
/**
* Test the setLocale function.
*
* @return void
*/
public function testSetLocale()
{
$this->localeManager->setLocale(new Locale('foo_BAR'));
$locale = $this->localeManager->getLocale();
$this->assertInstanceOf(Locale::class, $locale);
$this->assertEquals('foo_BAR', (string) $locale);
}
}

View file

@ -0,0 +1,107 @@
<?php
/**
* LocaleMiddlewareTest class.
*/
namespace Alltube\Test;
use Alltube\LocaleManager;
use Alltube\LocaleMiddleware;
use Slim\Container;
use Slim\Http\Environment;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* Unit tests for the FrontController class.
*/
class LocaleMiddlewareTest extends \PHPUnit_Framework_TestCase
{
/**
* LocaleMiddleware instance.
*
* @var LocaleMiddleware
*/
private $middleware;
/**
* Prepare tests.
*/
protected function setUp()
{
$container = new Container();
$container['locale'] = new LocaleManager();
$this->middleware = new LocaleMiddleware($container);
}
/**
* Test the testLocale() function.
*
* @return void
*/
public function testTestLocale()
{
$locale = [
'language'=> 'fr',
'region' => 'FR',
];
$this->assertEquals('fr_FR', $this->middleware->testLocale($locale));
}
/**
* Test the testLocale() function with an unsupported locale.
*
* @return void
*/
public function testLocaleWithWrongLocale()
{
$locale = [
'language'=> 'foo',
'region' => 'BAR',
];
$this->assertNull($this->middleware->testLocale($locale));
$this->assertNull($this->middleware->testLocale([]));
}
/**
* Test the __invoke() function.
*
* @return void
*/
public function testInvoke()
{
$request = Request::createFromEnvironment(Environment::mock());
$this->middleware->__invoke(
$request->withHeader('Accept-Language', 'fr-FR'),
new Response(),
function () {
}
);
}
/**
* Test the __invoke() function withot the Accept-Language header.
*
* @return void
*/
public function testInvokeWithoutHeader()
{
$request = Request::createFromEnvironment(Environment::mock());
$this->middleware->__invoke(
$request->withoutHeader('Accept-Language'),
new Response(),
function () {
}
);
}
/**
* 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');
}
}

79
tests/LocaleTest.php Normal file
View file

@ -0,0 +1,79 @@
<?php
/**
* LocaleTest class.
*/
namespace Alltube\Test;
use Alltube\Locale;
/**
* Unit tests for the Config class.
*/
class LocaleTest extends \PHPUnit_Framework_TestCase
{
/**
* Locale class instance.
*
* @var Locale
*/
private $locale;
/**
* Prepare tests.
*/
protected function setUp()
{
$this->locale = new Locale('fr_FR');
}
/**
* Test the __toString function.
*
* @return void
*/
public function testGetToString()
{
$this->assertEquals('fr_FR', $this->locale->__toString());
}
/**
* Test the getFullName function.
*
* @return void
*/
public function testGetFullName()
{
$this->assertEquals('français (France)', $this->locale->getFullName());
}
/**
* Test the getIso15897 function.
*
* @return void
*/
public function testGetIso15897()
{
$this->assertEquals('fr_FR', $this->locale->getIso15897());
}
/**
* Test the getBcp47 function.
*
* @return void
*/
public function testGetBcp47()
{
$this->assertEquals('fr-FR', $this->locale->getBcp47());
}
/**
* Test the getIso3166 function.
*
* @return void
*/
public function testGetIso3166()
{
$this->assertEquals('fr', $this->locale->getIso3166());
}
}

View file

@ -0,0 +1,105 @@
<?php
/**
* PlaylistArchiveStreamTest class.
*/
namespace Alltube\Test;
use Alltube\PlaylistArchiveStream;
/**
* Unit tests for the ViewFactory class.
*/
class PlaylistArchiveStreamTest extends \PHPUnit_Framework_TestCase
{
/**
* PlaylistArchiveStream instance.
*
* @var PlaylistArchiveStream
*/
private $stream;
/**
* Prepare tests.
*/
protected function setUp()
{
$this->stream = new PlaylistArchiveStream();
}
/**
* Test the stream_open() function.
*
* @return void
*/
public function testStreamOpen()
{
$this->assertTrue($this->stream->stream_open('playlist://foo'));
}
/**
* Test the stream_write() function.
*
* @return void
*/
public function testStreamWrite()
{
$this->assertEquals(0, $this->stream->stream_write());
}
/**
* Test the stream_stat() function.
*
* @return void
*/
public function testStreamStat()
{
$this->assertEquals(['mode'=>4096], $this->stream->stream_stat());
}
/**
* Test the stream_tell() function.
*
* @return void
*/
public function testStreamTell()
{
$this->stream->stream_open('playlist://foo');
$this->assertInternalType('int', $this->stream->stream_tell());
}
/**
* Test the stream_seek() function.
*
* @return void
*/
public function testStreamSeek()
{
$this->stream->stream_open('playlist://foo');
$this->assertInternalType('bool', $this->stream->stream_seek(3));
}
/**
* Test the stream_read() function.
*
* @return void
*/
public function testStreamRead()
{
$this->stream->stream_open('playlist://BaW_jenozKc;BaW_jenozKc/worst');
while (!$this->stream->stream_eof()) {
$this->assertLessThanOrEqual(8192, strlen($this->stream->stream_read(8192)));
}
}
/**
* Test the stream_eof() function.
*
* @return void
*/
public function testStreamEof()
{
$this->stream->stream_open('playlist://foo');
$this->assertFalse($this->stream->stream_eof());
}
}

View file

@ -25,7 +25,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
protected function setUp()
{
$this->download = new VideoDownload(Config::getInstance('config_test.yml'));
$this->download = new VideoDownload(Config::getInstance('config/config_test.yml'));
}
/**
@ -151,32 +151,32 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
return [
[
'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'best[protocol^=http]',
"It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU",
'It_s_Not_Me_It_s_You_-_Hearts_Under_Fire-M7IpKCZ47pU',
'mp4',
'googlevideo.com',
],
[
'https://www.youtube.com/watch?v=RJJ6FCAXvKg', 22,
"'Heart Attack' - Demi Lovato ".
'(Sam Tsui & Against The Current)-RJJ6FCAXvKg',
'Heart_Attack_-_Demi_Lovato_'.
'Sam_Tsui_Against_The_Current-RJJ6FCAXvKg',
'mp4',
'googlevideo.com',
],
[
'https://vimeo.com/24195442', 'best[protocol^=http]',
'Carving the Mountains-24195442',
'Carving_the_Mountains-24195442',
'mp4',
'vimeocdn.com',
],
[
'http://www.bbc.co.uk/programmes/b039g8p7', 'bestaudio/best',
'Leonard Cohen, Kaleidoscope - BBC Radio 4-b039d07m',
'Leonard_Cohen_Kaleidoscope_-_BBC_Radio_4-b039d07m',
'flv',
'bbcodspdns.fcod.llnwd.net',
],
[
'http://www.rtl2.de/sendung/grip-das-motormagazin/folge/folge-203-0', 'bestaudio/best',
'GRIP sucht den Sommerkönig-folge-203-0',
'GRIP_sucht_den_Sommerkonig-folge-203-0',
'f4v',
'edgefcs.net',
],
@ -193,7 +193,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
return [
[
'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'bestvideo+bestaudio',
"It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU",
'It_s_Not_Me_It_s_You_-_Hearts_Under_Fire-M7IpKCZ47pU',
'mp4',
'googlevideo.com',
],
@ -210,7 +210,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
return [
[
'https://twitter.com/verge/status/813055465324056576/video/1', 'best',
'The Verge - This tiny origami robot can self-fold and complete tasks-813055465324056576',
'The_Verge_-_This_tiny_origami_robot_can_self-fold_and_complete_tasks-813055465324056576',
'mp4',
'video.twimg.com',
],
@ -226,10 +226,10 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
{
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',
'http://www.canalc2.tv/video/12163', 'rtmp',
'Terrasses_du_Numerique-12163',
'flv',
'vod-flash.u-strasbg.fr',
],
];
}
@ -480,4 +480,18 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
$video = $download->getJSON($url, $format);
$download->getM3uStream($video);
}
/**
* Test getPlaylistArchiveStream function without avconv.
*
* @return void
*/
public function testGetPlaylistArchiveStream()
{
$video = $this->download->getJSON(
'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC',
'best'
);
$this->assertStream($this->download->getPlaylistArchiveStream($video, 'best'));
}
}

View file

@ -7,6 +7,8 @@ namespace Alltube\Test;
use Alltube\ViewFactory;
use Slim\Container;
use Slim\Http\Environment;
use Slim\Http\Request;
use Slim\Views\Smarty;
/**
@ -24,4 +26,16 @@ class ViewFactoryTest extends \PHPUnit_Framework_TestCase
$view = ViewFactory::create(new Container());
$this->assertInstanceOf(Smarty::class, $view);
}
/**
* Test the create() function with a X-Forwarded-Proto header.
*
* @return void
*/
public function testCreateWithXForwardedProto()
{
$request = Request::createFromEnvironment(Environment::mock());
$view = ViewFactory::create(new Container(), $request->withHeader('X-Forwarded-Proto', 'https'));
$this->assertInstanceOf(Smarty::class, $view);
}
}

View file

@ -2,6 +2,7 @@
/**
* File used to bootstrap tests.
*/
use Alltube\PlaylistArchiveStream;
/**
* Composer autoload.
@ -9,3 +10,5 @@
require_once __DIR__.'/../vendor/autoload.php';
session_start();
stream_wrapper_register('playlist', PlaylistArchiveStream::class);