Merge branch 'release/3.1.0'

This commit is contained in:
Pierre Rudloff 2022-10-16 15:36:37 +02:00
commit 36a91c8d4d
64 changed files with 2006 additions and 1336 deletions

24
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,24 @@
---
name: Tests
on:
- push
- pull_request
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
php-version:
- '7.3'
- '7.4'
steps:
- uses: actions/checkout@v2
- name: Use PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer
- run: composer install --no-progress
- run: composer check-platform-reqs
- run: composer lint
- run: composer test

View File

@ -18,9 +18,6 @@ FileETag None
<ifmodule mod_rewrite.c> <ifmodule mod_rewrite.c>
RewriteEngine On RewriteEngine On
RewriteCond %{HTTP_HOST} ^alltube\.herokuapp\.com$ [NC]
RewriteRule ^(.*)$ https://www.alltubedownload.net/$1 [R=301,L]
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L] RewriteRule ^ index.php [QSA,L]
</ifmodule> </ifmodule>

View File

@ -1,12 +0,0 @@
---
language: php
php: 7.3
addons:
apt:
packages:
- language-pack-fr
install: composer install --no-progress
script:
- composer check-platform-reqs
- composer lint
- composer test

View File

@ -1,6 +1,6 @@
# AllTube Download # AllTube Download
HTML GUI for youtube-dl ([alltubedownload.net](http://alltubedownload.net/)) HTML GUI for youtube-dl
![Screenshot](img/screenshot.png "AllTube GUI screenshot") ![Screenshot](img/screenshot.png "AllTube GUI screenshot")
@ -79,6 +79,7 @@ If you want to serve the application under a basepath and/or with a different in
* X-Forwarded-Host (ex. `another.domain.com`) * X-Forwarded-Host (ex. `another.domain.com`)
* X-Forwarded-Path (ex: `/alltube`) * X-Forwarded-Path (ex: `/alltube`)
* X-Forwarded-Port (ex: `5555`) * X-Forwarded-Port (ex: `5555`)
* X-Forwarded-Proto (ex: `https`)
### Apache ### Apache
@ -164,7 +165,7 @@ so that you can reuse it in your projects.
## JSON API ## JSON API
We also provide a JSON API that you can use like this: We also provide a JSON API that you can use like this:
[/json?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ](https://alltubedownload.net/json?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ) `/json?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ`
It returns a JSON object generated by youtube-dl. It returns a JSON object generated by youtube-dl.
You can find a list of all the properties [in the youtube-dl documentation](https://github.com/ytdl-org/youtube-dl#output-template). You can find a list of all the properties [in the youtube-dl documentation](https://github.com/ytdl-org/youtube-dl#output-template).

View File

@ -2,7 +2,6 @@
"name": "AllTube Download", "name": "AllTube Download",
"description": "HTML GUI for youtube-dl", "description": "HTML GUI for youtube-dl",
"repository": "https://github.com/Rudloff/alltube.git", "repository": "https://github.com/Rudloff/alltube.git",
"logo": "https://alltubedownload.net/img/logo.png",
"keywords": [ "keywords": [
"alltube", "alltube",
"download", "download",
@ -28,6 +27,5 @@
"value": "false", "value": "false",
"required": false "required": false
} }
}, }
"website": "https://alltubedownload.net/"
} }

View File

@ -62,7 +62,7 @@ class App extends \Slim\App
// Middlewares. // Middlewares.
$this->add(new LocaleMiddleware($container)); $this->add(new LocaleMiddleware($container));
$this->add(new CspMiddleware($container)); $this->add(new CspMiddleware($container));
$this->add(new LinkHeaderMiddleware($container)); $this->add(new LinkHeaderMiddleware());
$this->add(new RouterPathMiddleware($container)); $this->add(new RouterPathMiddleware($container));
// Controllers. // Controllers.
@ -94,7 +94,7 @@ class App extends \Slim\App
$this->any( $this->any(
'/watch', '/watch',
[$frontController, 'info'] [$frontController, 'watch']
); );
$this->any( $this->any(

View File

@ -154,7 +154,7 @@ class Config
/** /**
* Config constructor. * Config constructor.
* *
* @param mixed[] $options Options * @param scalar[]|scalar[][]|null[] $options Options
* @throws ConfigException * @throws ConfigException
*/ */
public function __construct(array $options = []) public function __construct(array $options = [])
@ -205,7 +205,7 @@ class Config
* @throws ConfigException If Python is missing * @throws ConfigException If Python is missing
* @throws ConfigException If youtube-dl is missing * @throws ConfigException If youtube-dl is missing
*/ */
private function validateOptions() private function validateOptions(): void
{ {
if (!is_file($this->youtubedl)) { if (!is_file($this->youtubedl)) {
throw new ConfigException("Can't find youtube-dl at " . $this->youtubedl); throw new ConfigException("Can't find youtube-dl at " . $this->youtubedl);
@ -222,11 +222,11 @@ class Config
/** /**
* Apply the provided options. * Apply the provided options.
* *
* @param mixed[] $options Options * @param scalar[]|scalar[][]|null[] $options Options
* *
* @return void * @return void
*/ */
private function applyOptions(array $options) private function applyOptions(array $options): void
{ {
foreach ($options as $option => $value) { foreach ($options as $option => $value) {
if (isset($this->$option) && isset($value)) { if (isset($this->$option) && isset($value)) {
@ -243,7 +243,7 @@ class Config
* @return void * @return void
* @throws ConfigException * @throws ConfigException
*/ */
private function getEnv() private function getEnv(): void
{ {
foreach (get_object_vars($this) as $prop => $value) { foreach (get_object_vars($this) as $prop => $value) {
try { try {
@ -278,11 +278,11 @@ class Config
/** /**
* Manually set some options. * Manually set some options.
* *
* @param mixed[] $options Options (see `config/config.example.yml` for available options) * @param scalar[]|scalar[][]|null[] $options Options (see `config/config.example.yml` for available options)
* @return void * @return void
* @throws ConfigException * @throws ConfigException
*/ */
public function setOptions(array $options) public function setOptions(array $options): void
{ {
$this->applyOptions($options); $this->applyOptions($options);
$this->validateOptions(); $this->validateOptions();

View File

@ -169,10 +169,8 @@ abstract class BaseController
*/ */
protected function getVideoPageUrl(Request $request): string protected function getVideoPageUrl(Request $request): string
{ {
$url = $request->getQueryParam('url') ?: $request->getQueryParam('v');
// Prevent SSRF attacks. // Prevent SSRF attacks.
$parts = Url::validateUrl($url, new Options()); $parts = Url::validateUrl($request->getQueryParam('url'), new Options());
return $parts['url']; return $parts['url'];
} }

View File

@ -220,13 +220,12 @@ class DownloadController extends BaseController
if ($request->isGet()) { if ($request->isGet()) {
$response = $response->withBody($body); $response = $response->withBody($body);
} }
$response = $response->withHeader(
return $response->withHeader(
'Content-Disposition', 'Content-Disposition',
'attachment; filename="' . 'attachment; filename="' .
$this->video->getFilename() . '"' $this->video->getFilename() . '"'
); );
return $response;
} }
/** /**

View File

@ -14,6 +14,8 @@ use Alltube\Middleware\CspMiddleware;
use Exception; use Exception;
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException; use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException;
use Slim\Http\StatusCode; use Slim\Http\StatusCode;
use Slim\Http\Uri;
use stdClass;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Throwable; use Throwable;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
@ -146,7 +148,7 @@ class FrontController extends BaseController
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
private function getInfoResponse(Request $request, Response $response) private function getInfoResponse(Request $request, Response $response): Response
{ {
try { try {
$this->video->getJson(); $this->video->getJson();
@ -176,6 +178,50 @@ class FrontController extends BaseController
] ]
); );
} }
$formats = [];
$genericFormatsLabel = $this->localeManager->t('Generic formats');
$detailedFormatsLabel = $this->localeManager->t('Detailed formats');
foreach ($this->config->genericFormats as $id => $genericFormat) {
$formats[$genericFormatsLabel][$id] = $this->localeManager->t($genericFormat);
}
$json = $this->video->getJson();
if (isset($json->formats)) {
/** @var stdClass $format */
foreach ($json->formats as $format) {
if ($this->config->stream || in_array($format->protocol, ['http', 'https'])) {
$formatParts = [
// File extension
$format->ext,
];
if (isset($format->width) || isset($format->height)) {
// Video dimensions
$formatParts[] = implode('x', array_filter([$format->width, $format->height]));
}
if (isset($format->filesize)) {
// File size
$formatParts[] = round($format->filesize / 1000000, 2) . ' MB';
}
if (isset($format->format_note)) {
// Format name
$formatParts[] = $format->format_note;
}
if (isset($format->format_id)) {
// Format ID
$formatParts[] = '(' . $format->format_id . ')';
}
$formats[$detailedFormatsLabel][$format->format_id] = implode(' ', $formatParts);
}
}
}
$this->view->render( $this->view->render(
$response, $response,
$template, $template,
@ -185,6 +231,7 @@ class FrontController extends BaseController
'title' => $title, 'title' => $title,
'description' => $description, 'description' => $description,
'defaultFormat' => $this->defaultFormat, 'defaultFormat' => $this->defaultFormat,
'formats' => $formats
] ]
); );
@ -302,4 +349,24 @@ class FrontController extends BaseController
return $this->displayError($request, $response, $message); return $this->displayError($request, $response, $message);
} }
} }
/**
* Route that mimics YouTube video URLs ("/watch?v=foo")
*
* @param Request $request
* @param Response $response
* @return Response
*/
public function watch(Request $request, Response $response): Response
{
// We build a full YouTube URL from the video ID.
$youtubeUri = Uri::createFromString('https://www.youtube.com/watch')
->withQuery(http_build_query(['v' => $request->getQueryParam('v')]));
// Then pass it to the info route.
return $response->withRedirect(
Uri::createFromString($this->router->pathFor('info'))
->withQuery(http_build_query(['url' => strval($youtubeUri)]))
);
}
} }

View File

@ -7,7 +7,6 @@
namespace Alltube\Controller; namespace Alltube\Controller;
use Alltube\Library\Exception\AlltubeLibraryException; use Alltube\Library\Exception\AlltubeLibraryException;
use Exception;
use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException; use Graby\HttpClient\Plugin\ServerSideRequestForgeryProtection\Exception\InvalidURLException;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -25,6 +24,7 @@ class JsonController extends BaseController
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException
*/ */
public function json(Request $request, Response $response): Response public function json(Request $request, Response $response): Response
{ {

View File

@ -2,6 +2,7 @@
namespace Alltube; namespace Alltube;
use Slim\Http\StatusCode;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Throwable; use Throwable;
@ -17,7 +18,7 @@ class ErrorHandler
* @param Throwable $e * @param Throwable $e
* @return void * @return void
*/ */
public static function handle(Throwable $e) public static function handle(Throwable $e): void
{ {
error_log($e); error_log($e);
@ -29,7 +30,7 @@ class ErrorHandler
http_response_code($exception->getStatusCode()); http_response_code($exception->getStatusCode());
die($exception->getAsString()); die($exception->getAsString());
} else { } else {
http_response_code(500); http_response_code(StatusCode::HTTP_INTERNAL_SERVER_ERROR);
die('Error when starting the app: ' . htmlentities($e->getMessage())); die('Error when starting the app: ' . htmlentities($e->getMessage()));
} }
} }

View File

@ -4,6 +4,7 @@ namespace Alltube\Factory;
use Alltube\Exception\DependencyException; use Alltube\Exception\DependencyException;
use Alltube\LocaleManager; use Alltube\LocaleManager;
use Locale;
use Slim\Container; use Slim\Container;
/** /**
@ -20,7 +21,7 @@ class LocaleManagerFactory
*/ */
public static function create(Container $container): LocaleManager public static function create(Container $container): LocaleManager
{ {
if (!class_exists('Locale')) { if (!class_exists(Locale::class)) {
throw new DependencyException('You need to install the intl extension for PHP.'); throw new DependencyException('You need to install the intl extension for PHP.');
} }

View File

@ -7,6 +7,7 @@ use Consolidation\Log\LoggerManager;
use Consolidation\Log\LogOutputStyler; use Consolidation\Log\LogOutputStyler;
use Slim\Container; use Slim\Container;
use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
/** /**
* Class LoggerFactory * Class LoggerFactory
@ -23,9 +24,9 @@ class LoggerFactory
{ {
$config = $container->get('config'); $config = $container->get('config');
if ($config->debug) { if ($config->debug) {
$verbosity = ConsoleOutput::VERBOSITY_DEBUG; $verbosity = OutputInterface::VERBOSITY_DEBUG;
} else { } else {
$verbosity = ConsoleOutput::VERBOSITY_NORMAL; $verbosity = OutputInterface::VERBOSITY_NORMAL;
} }
$loggerManager = new LoggerManager(); $loggerManager = new LoggerManager();

View File

@ -7,6 +7,7 @@
namespace Alltube\Factory; namespace Alltube\Factory;
use Aura\Session\Session; use Aura\Session\Session;
use Aura\Session\SessionFactory as AuraSessionFactory;
use Slim\Container; use Slim\Container;
/** /**
@ -23,7 +24,7 @@ class SessionFactory
*/ */
public static function create(Container $container): Session public static function create(Container $container): Session
{ {
$session_factory = new \Aura\Session\SessionFactory(); $session_factory = new AuraSessionFactory();
$session = $session_factory->newInstance($_COOKIE); $session = $session_factory->newInstance($_COOKIE);
$session->setCookieParams(['httponly' => true]); $session->setCookieParams(['httponly' => true]);

View File

@ -20,22 +20,6 @@ use SmartyException;
*/ */
class ViewFactory class ViewFactory
{ {
/**
* Generate the canonical URL of the current page.
*
* @param Request $request PSR-7 Request
*
* @return string URL
*/
private static function getCanonicalUrl(Request $request): string
{
/** @var Uri $uri */
$uri = $request->getUri();
return $uri->withBasePath('')
->withHost('alltubedownload.net')
->withScheme('https');
}
/** /**
* @param Uri $uri * @param Uri $uri
@ -66,22 +50,13 @@ class ViewFactory
} }
/** /**
* Create Smarty view object. * Create a URI suitable for templates.
* *
* @param ContainerInterface $container Slim dependency container * @param Request $request
* @param Request|null $request PSR-7 request * @return Uri
*
* @return Smarty
* @throws SmartyException
*/ */
public static function create(ContainerInterface $container, Request $request = null): Smarty public static function prepareUri(Request $request): Uri
{ {
if (!isset($request)) {
$request = $container->get('request');
}
$view = new Smarty($container->get('root_path') . '/templates/');
/** @var Uri $uri */ /** @var Uri $uri */
$uri = $request->getUri(); $uri = $request->getUri();
if (in_array('https', $request->getHeader('X-Forwarded-Proto'))) { if (in_array('https', $request->getHeader('X-Forwarded-Proto'))) {
@ -101,25 +76,45 @@ class ViewFactory
$uri = $uri->withBasePath($path); $uri = $uri->withBasePath($path);
} }
return self::cleanBasePath($uri);
}
/**
* Create Smarty view object.
*
* @param ContainerInterface $container Slim dependency container
* @param Request|null $request PSR-7 request
*
* @return Smarty
* @throws SmartyException
*/
public static function create(ContainerInterface $container, Request $request = null): Smarty
{
if (!isset($request)) {
$request = $container->get('request');
}
$view = new Smarty($container->get('root_path') . '/templates/');
$uri = self::prepareUri($request);
/** @var LocaleManager $localeManager */ /** @var LocaleManager $localeManager */
$localeManager = $container->get('locale'); $localeManager = $container->get('locale');
$uri = self::cleanBasePath($uri);
$smartyPlugins = new SmartyPlugins($container->get('router'), $uri->withUserInfo('')); $smartyPlugins = new SmartyPlugins($container->get('router'), $uri->withUserInfo(''));
$view->registerPlugin('function', 'path_for', [$smartyPlugins, 'pathFor']); $view->registerPlugin('function', 'path_for', [$smartyPlugins, 'pathFor']);
$view->registerPlugin('function', 'base_url', [$smartyPlugins, 'baseUrl']); $view->registerPlugin('function', 'base_url', [$smartyPlugins, 'baseUrl']);
$view->registerPlugin('block', 't', [$localeManager, 'smartyTranslate']); $view->registerPlugin('block', 't', [$localeManager, 'smartyTranslate']);
$view->offsetSet('canonical', self::getCanonicalUrl($request));
$view->offsetSet('locale', $container->get('locale')); $view->offsetSet('locale', $container->get('locale'));
$view->offsetSet('config', $container->get('config')); $view->offsetSet('config', $container->get('config'));
$view->offsetSet('domain', $uri->withBasePath('')->getBaseUrl()); $view->offsetSet('domain', $uri->withBasePath('')->getBaseUrl());
if ($container->has('debugbar')) { if ($container->has('debugbar')) {
$debugBar = $container->get('debugbar'); $debugBar = $container->get('debugbar');
$collector = new SmartyCollector($view->getSmarty());
$debugBar->addCollector(new SmartyCollector($view->getSmarty())); $collector->useHtmlVarDumper();
$debugBar->addCollector($collector);
$view->offsetSet( $view->offsetSet(
'debug_render', 'debug_render',

View File

@ -114,7 +114,7 @@ class LocaleManager
* @param Locale $locale Locale * @param Locale $locale Locale
* @return void * @return void
*/ */
public function setLocale(Locale $locale) public function setLocale(Locale $locale): void
{ {
$this->translator->setLocale($locale->getIso15897()); $this->translator->setLocale($locale->getIso15897());
$this->curLocale = $locale; $this->curLocale = $locale;
@ -125,7 +125,7 @@ class LocaleManager
* Unset the current locale. * Unset the current locale.
* @return void * @return void
*/ */
public function unsetLocale() public function unsetLocale(): void
{ {
$this->translator->setLocale(self::DEFAULT_LOCALE); $this->translator->setLocale(self::DEFAULT_LOCALE);
$this->curLocale = null; $this->curLocale = null;
@ -135,14 +135,14 @@ class LocaleManager
/** /**
* Smarty "t" block. * Smarty "t" block.
* *
* @param mixed[] $params Block parameters * @param string[]|string[][] $params Block parameters
* @param string|null $text Block content * @param string|null $text Block content
* *
* @return string Translated string * @return string Translated string
*/ */
public function smartyTranslate(array $params, string $text = null): string public function smartyTranslate(array $params, string $text = null): string
{ {
if (isset($params['params'])) { if (isset($params['params']) && is_array($params['params'])) {
return $this->t($text, $params['params']); return $this->t($text, $params['params']);
} else { } else {
return $this->t($text); return $this->t($text);
@ -154,7 +154,7 @@ class LocaleManager
* *
* @param string|null $string $string String to translate * @param string|null $string $string String to translate
* *
* @param mixed[] $params * @param string[] $params
* @return string Translated string * @return string Translated string
*/ */
public function t(string $string = null, array $params = []): string public function t(string $string = null, array $params = []): string

View File

@ -2,10 +2,9 @@
namespace Alltube\Middleware; namespace Alltube\Middleware;
use Psr\Container\ContainerInterface; use Alltube\Factory\ViewFactory;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use Slim\Router;
/** /**
* Class LinkHeaderMiddleware * Class LinkHeaderMiddleware
@ -13,19 +12,6 @@ use Slim\Router;
*/ */
class LinkHeaderMiddleware class LinkHeaderMiddleware
{ {
/**
* @var Router
*/
private $router;
/**
* LinkHeaderMiddleware constructor.
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->router = $container->get('router');
}
/** /**
* @param Request $request * @param Request $request
@ -35,12 +21,19 @@ class LinkHeaderMiddleware
*/ */
public function __invoke(Request $request, Response $response, callable $next) public function __invoke(Request $request, Response $response, callable $next)
{ {
$uri = ViewFactory::prepareUri($request);
$response = $response->withHeader( $response = $response->withHeader(
'Link', 'Link',
'<' . $this->router->getBasePath() . '/css/style.css>; rel=preload; as=style' implode(
'; ',
[
'<' . $uri->getBasePath() . '/css/style.css>',
'rel=preload', 'as=style'
]
)
); );
return $next($request, $response); return $next($request, $response);
} }
} }

View File

@ -38,7 +38,7 @@ class LocaleMiddleware
/** /**
* Test if a locale can be used for the current user. * Test if a locale can be used for the current user.
* *
* @param mixed[] $proposedLocale Locale array created by AcceptLanguage::parse() * @param string[] $proposedLocale Locale array created by AcceptLanguage::parse()
* *
* @return Locale|null Locale if chosen, nothing otherwise * @return Locale|null Locale if chosen, nothing otherwise
*/ */

View File

@ -31,7 +31,7 @@ class ReleaseCommand extends Tasks
$tmpDir = $this->_tmpDir(); $tmpDir = $this->_tmpDir();
$filename = 'alltube-' . trim((string)$result->getMessage()) . '.zip'; $filename = 'alltube-' . trim($result->getMessage()) . '.zip';
/** @var FilesystemStack $rmTask */ /** @var FilesystemStack $rmTask */
$rmTask = $this->taskFilesystemStack(); $rmTask = $this->taskFilesystemStack();

View File

@ -23,7 +23,7 @@ class ConvertedPlaylistArchiveStream extends PlaylistArchiveStream
* @return void * @return void
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
protected function startVideoStream(Video $video) protected function startVideoStream(Video $video): void
{ {
$this->curVideoStream = new Stream($this->downloader->getAudioStream($video)); $this->curVideoStream = new Stream($this->downloader->getAudioStream($video));

View File

@ -83,7 +83,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return void * @return void
*/ */
protected function send($data) protected function send($data): void
{ {
$pos = $this->tell(); $pos = $this->tell();
@ -133,7 +133,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return void * @return void
*/ */
public function rewind() public function rewind(): void
{ {
rewind($this->buffer); rewind($this->buffer);
} }
@ -233,7 +233,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return void * @return void
*/ */
public function seek($offset, $whence = SEEK_SET) public function seek($offset, $whence = SEEK_SET): void
{ {
fseek($this->buffer, $offset, $whence); fseek($this->buffer, $offset, $whence);
} }
@ -256,7 +256,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* @return void * @return void
* @throws AlltubeLibraryException * @throws AlltubeLibraryException
*/ */
protected function startVideoStream(Video $video) protected function startVideoStream(Video $video): void
{ {
$response = $this->downloader->getHttpResponse($video); $response = $this->downloader->getHttpResponse($video);
@ -320,7 +320,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* *
* @return void * @return void
*/ */
public function close() public function close(): void
{ {
if (is_resource($this->buffer)) { if (is_resource($this->buffer)) {
fclose($this->buffer); fclose($this->buffer);

View File

@ -63,7 +63,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return void * @return void
*/ */
public function close() public function close(): void
{ {
$this->response->getBody()->close(); $this->response->getBody()->close();
} }
@ -126,7 +126,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return void * @return void
*/ */
public function seek($offset, $whence = SEEK_SET) public function seek($offset, $whence = SEEK_SET): void
{ {
$this->response->getBody()->seek($offset, $whence); $this->response->getBody()->seek($offset, $whence);
} }
@ -136,7 +136,7 @@ class YoutubeChunkStream implements StreamInterface
* *
* @return void * @return void
*/ */
public function rewind() public function rewind(): void
{ {
$this->response->getBody()->rewind(); $this->response->getBody()->rewind();
} }
@ -156,9 +156,9 @@ class YoutubeChunkStream implements StreamInterface
* *
* @param mixed $string The string that is to be written * @param mixed $string The string that is to be written
* *
* @return mixed * @return int
*/ */
public function write($string) public function write($string): int
{ {
return $this->response->getBody()->write($string); return $this->response->getBody()->write($string);
} }

View File

@ -22,7 +22,7 @@ class UglyRouter extends Router
* *
* @param ServerRequestInterface $request The current HTTP request object * @param ServerRequestInterface $request The current HTTP request object
* *
* @return mixed[] * @return int[]|string[]|array[]
* *
* @link https://github.com/nikic/FastRoute/blob/master/src/Dispatcher.php * @link https://github.com/nikic/FastRoute/blob/master/src/Dispatcher.php
*/ */
@ -55,7 +55,7 @@ class UglyRouter extends Router
*/ */
public function pathFor($name, array $data = [], array $queryParams = []): string public function pathFor($name, array $data = [], array $queryParams = []): string
{ {
$queryParams['page'] = $name; $queryParams['page'] = $this->relativePathFor($name, $data);
$url = Uri::createFromString($this->relativePathFor($name, $data, $queryParams))->withPath(''); $url = Uri::createFromString($this->relativePathFor($name, $data, $queryParams))->withPath('');
if ($this->basePath) { if ($this->basePath) {

View File

@ -1,9 +1,8 @@
{ {
"name": "rudloff/alltube", "name": "rudloff/alltube",
"type": "project",
"description": "HTML GUI for youtube-dl", "description": "HTML GUI for youtube-dl",
"homepage": "http://alltubedownload.net/",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"type": "project",
"authors": [ "authors": [
{ {
"name": "Pierre Rudloff", "name": "Pierre Rudloff",
@ -14,7 +13,7 @@
{ {
"name": "Olivier Haquette", "name": "Olivier Haquette",
"email": "contact@olivierhaquette.fr", "email": "contact@olivierhaquette.fr",
"homepage": "http://olivierhaquette.fr/", "homepage": "https://ographik.fr/",
"role": "Designer" "role": "Designer"
} }
], ],
@ -29,22 +28,22 @@
"j0k3r/httplug-ssrf-plugin": "^2.0", "j0k3r/httplug-ssrf-plugin": "^2.0",
"jawira/case-converter": "^3.4", "jawira/case-converter": "^3.4",
"jean85/pretty-package-versions": "^1.3", "jean85/pretty-package-versions": "^1.3",
"mathmarques/smarty-view": "^1.1", "mathmarques/smarty-view": "^1.2",
"oomphinc/composer-installers-extender": "^2.0", "oomphinc/composer-installers-extender": "^2.0",
"paragonie/csp-builder": "^2.5", "paragonie/csp-builder": "^2.5",
"rinvex/countries": "^6.1", "rinvex/countries": "^6.1",
"rudloff/alltube-library": "^0.1.1", "rudloff/alltube-library": "^0.1.3",
"symfony/finder": "^5.0", "symfony/finder": "^5.4",
"symfony/translation": "^4.0", "symfony/translation": "^4.0",
"symfony/yaml": "^4.0", "symfony/yaml": "^4.0",
"webfontkit/open-sans": "^1.0", "webfontkit/open-sans": "^1.0",
"ytdl-org/youtube-dl": "^2021.04", "ytdl-org/youtube-dl": "^2021.12",
"zonuexe/http-accept-language": "^0.4.1" "zonuexe/http-accept-language": "^0.4.1"
}, },
"require-dev": { "require-dev": {
"consolidation/robo": "^2.1", "consolidation/robo": "^2.1",
"enlightn/security-checker": "^1.4", "enlightn/security-checker": "^1.4",
"ergebnis/composer-normalize": "^2.6", "ergebnis/composer-normalize": "^2.20",
"insite/composer-dangling-locked-deps": "^0.2.1", "insite/composer-dangling-locked-deps": "^0.2.1",
"junker/debugbar-smarty": "^0.1.0", "junker/debugbar-smarty": "^0.1.0",
"kitchenu/slim-debugbar": "^1.1", "kitchenu/slim-debugbar": "^1.1",
@ -52,13 +51,45 @@
"php-mock/php-mock-mockery": "^1.3", "php-mock/php-mock-mockery": "^1.3",
"phpro/grumphp": "^1.3", "phpro/grumphp": "^1.3",
"phpstan/phpstan": "^0.12.72", "phpstan/phpstan": "^0.12.72",
"phpunit/phpunit": "^8.4", "phpunit/phpunit": "^9.5",
"povils/phpmnd": "^2.5",
"smarty-gettext/smarty-gettext": "^1.6", "smarty-gettext/smarty-gettext": "^1.6",
"squizlabs/php_codesniffer": "^3.5", "squizlabs/php_codesniffer": "^3.5",
"symfony/error-handler": "^5.0", "symfony/error-handler": "^5.4",
"symfony/var-dumper": "^5.0" "symfony/var-dumper": "^5.4"
},
"repositories": [
{
"type": "package",
"package": {
"name": "ytdl-org/youtube-dl",
"version": "2021.12.17",
"dist": {
"type": "tar",
"url": "https://yt-dl.org/downloads/2021.12.17/youtube-dl-2021.12.17.tar.gz"
}
}
}
],
"autoload": {
"psr-4": {
"Alltube\\": "classes/"
}
},
"autoload-dev": {
"psr-4": {
"Alltube\\Test\\": "tests/"
}
}, },
"config": { "config": {
"allow-plugins": {
"composer/installers": true,
"cweagans/composer-patches": true,
"ergebnis/composer-normalize": true,
"insite/composer-dangling-locked-deps": true,
"oomphinc/composer-installers-extender": true,
"phpro/grumphp": true
},
"platform": { "platform": {
"php": "7.3.11" "php": "7.3.11"
}, },
@ -82,29 +113,6 @@
} }
} }
}, },
"autoload": {
"psr-4": {
"Alltube\\": "classes/"
}
},
"autoload-dev": {
"psr-4": {
"Alltube\\Test\\": "tests/"
}
},
"repositories": [
{
"type": "package",
"package": {
"name": "ytdl-org/youtube-dl",
"version": "2021.04.01",
"dist": {
"type": "zip",
"url": "https://github.com/ytdl-org/youtube-dl/archive/2021.04.01.zip"
}
}
}
],
"scripts": { "scripts": {
"lint": "grumphp run --ansi", "lint": "grumphp run --ansi",
"release": "robo release --ansi", "release": "robo release --ansi",

2128
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -255,7 +255,7 @@ footer a:hover {
margin-top: 12px; margin-top: 12px;
position: relative; position: relative;
text-align: left; text-align: left;
width: 622px; width: 100%;
} }
.mp3-inner { .mp3-inner {
@ -545,6 +545,7 @@ h1 {
.thumb { .thumb {
max-width: 700px; max-width: 700px;
height: auto;
} }
.format { .format {

View File

@ -13,6 +13,7 @@ grumphp:
securitychecker_enlightn: ~ securitychecker_enlightn: ~
composer_normalize: ~ composer_normalize: ~
composer_dangling_locked_deps: ~ composer_dangling_locked_deps: ~
phpmnd: ~
phpcs: phpcs:
standard: PSR12 standard: PSR12
phpstan: phpstan:

View File

@ -98,7 +98,7 @@ msgid "Share on Facebook"
msgstr "شاركها على فيسبوك" msgstr "شاركها على فيسبوك"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "انسخ هنا رابط الفيديو (يوتيوب، انستقرام، وغيرها)" msgstr "انسخ هنا رابط الفيديو (يوتيوب، انستقرام، وغيرها)"
#: templates/index.tpl:25 #: templates/index.tpl:25

View File

@ -131,8 +131,8 @@ msgid "Video password"
msgstr "Videopasswort" msgstr "Videopasswort"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Kopiere hier die URL deines Videos (Youtube, Dailymotion, etc.) hinein" msgstr "Kopiere hier die URL deines Videos (YouTube, Dailymotion, etc.) hinein"
#: templates/index.tpl:25 #: templates/index.tpl:25
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -106,8 +106,8 @@ msgid "Share on Facebook"
msgstr "Compartir en Facebook" msgstr "Compartir en Facebook"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Copia aquí la URL de tu vídeo (Youtube, Dailymotion, etc.)" msgstr "Copia aquí la URL de tu vídeo (YouTube, Dailymotion, etc.)"
#: templates/index.tpl:23 #: templates/index.tpl:23
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -59,8 +59,8 @@ msgid "Video password"
msgstr "Password del video" msgstr "Password del video"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Copia qui l'URL del video (Youtube, Dailymotion, ecc.)" msgstr "Copia qui l'URL del video (YouTube, Dailymotion, ecc.)"
#: templates/index.tpl:25 #: templates/index.tpl:25
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -134,7 +134,7 @@ msgstr "閲覧用パスワード"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "動画のリンク(URL)を入力欄に入力してください。(例:Youtube,Dailymotion等。)" msgstr "動画のリンク(URL)を入力欄に入力してください。(例:YouTube,Dailymotion等。)"
#: templates/index.tpl:25 #: templates/index.tpl:25
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -71,8 +71,8 @@ msgid "Video password"
msgstr "Hasło do wideo" msgstr "Hasło do wideo"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Zamieść link do wideo (Yotube, Dailymotion, itp.)" msgstr "Zamieść link do wideo (YouTube, Dailymotion, itp.)"
#: templates/index.tpl:25 #: templates/index.tpl:25
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -106,8 +106,8 @@ msgid "Share on Facebook"
msgstr "Compartilhe no Facebook" msgstr "Compartilhe no Facebook"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Cole aqui a URL do vídeo (Youtube, Dailymotion, etc.)" msgstr "Cole aqui a URL do vídeo (YouTube, Dailymotion, etc.)"
#: templates/index.tpl:24 #: templates/index.tpl:24
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -65,8 +65,8 @@ msgid "Video password"
msgstr "Video parolası" msgstr "Video parolası"
#: templates/index.tpl:8 #: templates/index.tpl:8
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "Videonuzun URL'sini buraya kopyalayın (Youtube, Dailymotion, vb.)" msgstr "Videonuzun URL'sini buraya kopyalayın (YouTube, Dailymotion, vb.)"
#: templates/index.tpl:25 #: templates/index.tpl:25
msgid "Audio only (MP3)" msgid "Audio only (MP3)"

View File

@ -1,134 +1,227 @@
msgid "" msgid ""
msgstr "" msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: POEditor.com\n"
"Project-Id-Version: AllTube Download\n"
"Language: zh-CN\n"
#: templates/error.tpl:6 #: templates/inc/footer.tpl:8
msgid "Please check the URL of your video." msgid "Code by @dev"
msgstr "请检查您的视频的 URL。" msgstr "由 @dev 开发"
#: 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:83
#: templates/video.tpl:86 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:24
msgid "Available formats:"
msgstr "可用的格式︰"
#: templates/video.tpl:29
msgid "Generic formats"
msgstr "通用格式"
#: templates/video.tpl:32
msgid "Best"
msgstr "最佳"
#: templates/video.tpl:37
msgid "Remux best video with best audio"
msgstr "重新封装最佳视频与最佳音频"
#: templates/video.tpl:41
msgid "Worst"
msgstr "最差"
#: templates/video.tpl:44
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 "去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 #: templates/inc/footer.tpl:16
msgid "Based on" msgid "Design by @designer"
msgstr "基于" msgstr "由 @designer 设计"
#: templates/inc/header.tpl:21 #: templates/inc/footer.tpl:21
msgid "Share on Twitter" msgid "Get the code"
msgstr "分享到 Twitter" msgstr "获取源代码"
#: templates/inc/header.tpl:23 #: templates/inc/footer.tpl:29
msgid "Share on Facebook" msgid "Based on @youtubedl"
msgstr "分享到 Facebook" msgstr "基于 @youtubedl"
#: templates/index.tpl:8 #: templates/inc/footer.tpl:33
msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" msgid "Donate using Liberapay"
msgstr "在这里复制您的视频 Youtube、 Dailymotion 等) 的 URL" msgstr "使用 Liberapay 捐赠"
#: templates/index.tpl:23 #: templates/inc/footer.tpl:35
msgid "Audio only (MP3)" msgid "Donate"
msgstr "仅限音频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 "书签工具"
#: templates/inc/header.tpl:4 #: templates/inc/header.tpl:4
msgid "Switch language" msgid "Switch language"
msgstr "切换语言" msgstr "切换语言"
#: templates/inc/header.tpl:8
msgid "Set language"
msgstr "设置语言"
#: templates/info.tpl:11
msgid "You are going to download @title."
msgstr "您将要下载 @title。"
#: templates/info.tpl:29
msgid "Available formats:"
msgstr "可用的格式:"
#: templates/info.tpl:31
msgid "Generic formats"
msgstr "通用格式"
#: templates/info.tpl:35
msgid "Best"
msgstr "最佳"
#: templates/info.tpl:36
msgid "Remux best video with best audio"
msgstr "重新封装最佳视频和最佳音频"
#: templates/info.tpl:37
msgid "Worst"
msgstr "最差"
#: templates/info.tpl:42
msgid "Detailed formats"
msgstr "详细格式"
#: templates/info.tpl:86
msgid "Stream the video through the server"
msgstr "通过服务器传输视频"
#: templates/info.tpl:92
msgid "Convert into a custom format:"
msgstr "转换为自定义格式:"
#: templates/info.tpl:93
msgid "Custom format"
msgstr "自定义格式"
#: templates/info.tpl:93
msgid "Format to convert to"
msgstr "要转换到的格式"
#: templates/info.tpl:98
# Other translators: Please check that file for context
msgid "with"
msgstr ",并带"
#: templates/info.tpl:99
msgid "Bit rate"
msgstr "比特率"
#: templates/info.tpl:100
msgid "Custom bitrate"
msgstr "自定义比特率"
#: templates/info.tpl:103
msgid "kbit/s audio"
msgstr "kbit/s 的音频"
#: templates/info.tpl:107 templates/playlist.tpl:38 templates/password.tpl:11
#: templates/index.tpl:19
msgid "Download"
msgstr "下载"
#: templates/playlist.tpl:12
msgid "Videos extracted from @title:"
msgstr "从 @title 中提取的视频:"
#: templates/playlist.tpl:39
msgid "More options"
msgstr "更多选项"
#: templates/extractors.tpl:4 classes/Controller/FrontController.php:111
msgid "Supported websites"
msgstr "支持的网站"
#: templates/error.tpl:5 #: templates/error.tpl:5
msgid "An error occurred" msgid "An error occurred"
msgstr "出错了" 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/index.tpl:8
# I don't think this needs to be a 100% match
msgid "Copy here the URL of your video (YouTube, Dailymotion, etc.)"
msgstr "在此处粘贴视频网址"
#: templates/index.tpl:25
msgid "Audio only (MP3)"
msgstr "仅音频MP3"
#: templates/index.tpl:29
# Still check that file for context
msgid "From"
msgstr "从"
#: templates/index.tpl:32
msgid "to"
msgstr "到"
#: templates/index.tpl:41
msgid "See all supported websites"
msgstr "查看所有支持的网站"
#: templates/index.tpl:43
msgid "Drag this to your bookmarks bar:"
msgstr "您可以把这个书签工具拖到您的书签栏中:"
#: templates/index.tpl:45
msgid "Bookmarklet"
msgstr "书签工具"
#: classes/Controller/DownloadController.php:64
#: classes/Controller/FrontController.php:166
msgid "Wrong password"
msgstr "密码错误"
#: classes/Controller/DownloadController.php:69
msgid "Conversion of playlists is not supported."
msgstr "不支持转换播放列表。"
#: classes/Controller/DownloadController.php:76
msgid "Conversion of M3U8 files is not supported."
msgstr "不支持转换 M3U8 文件。"
#: classes/Controller/DownloadController.php:82
# ref. Chinese Wikipedia article about DASH
msgid "Conversion of DASH segments is not supported."
msgstr "不支持转换 DASH 片段。"
#: classes/Controller/FrontController.php:65
msgid ""
"Easily download videos from YouTube, Dailymotion, Vimeo and other websites."
msgstr ""
"轻松从 YouTube、Dailymotion、Vimeo 等网站下载视频。"
#: classes/Controller/FrontController.php:112
# NOTE: DON'T translate AllTube Download
msgid ""
"List of all supported websites from which AllTube Download can extract video "
"or audio files"
msgstr ""
"AllTube Download 能够提取视频"
"或音频文件的的所有网站"
#: classes/Controller/FrontController.php:138
msgid "Password prompt"
msgstr "密码提示"
#: classes/Controller/FrontController.php:140
msgid ""
"You need a password in order to download this video with AllTube Download"
msgstr ""
"您需要密码才能使用 AllTube Download 下载此视频"
#: classes/Controller/FrontController.php:174
# Download page header?
msgid "Video download"
msgstr "下载视频"
#: classes/Controller/FrontController.php:176
msgid "Download video from @extractor"
msgstr "从 @extractor 下载视频"
#: classes/Controller/FrontController.php:182
msgid "Download @title from @extractor"
msgstr "从 @extractor 下载 @title"
#: classes/Controller/FrontController.php:255
msgid "Error"
msgstr "错误"
#: classes/Controller/FrontController.php:271
msgid "Page not found"
msgstr "找不到页面"
#: classes/Controller/FrontController.php:282
msgid "Method not allowed"
msgstr "不允许此请求方法"

View File

@ -1,10 +1,11 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<phpunit bootstrap="tests/bootstrap.php"> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="tests/bootstrap.php"
<filter> xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<whitelist> <coverage>
<include>
<directory>classes/</directory> <directory>classes/</directory>
</whitelist> </include>
</filter> </coverage>
<testsuites> <testsuites>
<testsuite name="Tests"> <testsuite name="Tests">
<directory>tests/</directory> <directory>tests/</directory>

View File

@ -6,19 +6,6 @@ Most recent browsers automatically play a video
if it is a format they know how to play. if it is a format they know how to play.
You can usually download the video by doing *File > Save to* or *ctrl + S*. You can usually download the video by doing *File > Save to* or *ctrl + S*.
## [alltubedownload.net](https://alltubedownload.net) is too slow
[alltubedownload.net](https://alltubedownload.net) is hosted on a free [Heroku server](https://www.heroku.com/pricing)
so it has low RAM and CPU.
AllTube probably won't switch to a more expensive hosting
because this project does not earn any financial resources
and you are encouraged to host it yourself.
## alltubedownload.net often says "An error occurred in the application…"
See above.
## Change config parameters ## Change config parameters
You need to create a YAML file called `config.yml` in the `config/` folder. You need to create a YAML file called `config.yml` in the `config/` folder.
@ -68,8 +55,7 @@ There are two known workarounds:
* You can run AllTube locally on your computer. * You can run AllTube locally on your computer.
* You can enable streaming videos through the server (see below). * You can enable streaming videos through the server (see below).
Please note that this can use a lot of resources on the server Please note that this can use a lot of resources on the server.
(which is why we won't enable it on alltubedownload.net).
## I get a 404 error on every page except the index ## I get a 404 error on every page except the index

View File

@ -31,7 +31,7 @@
} }
], ],
"lang": "en", "lang": "en",
"start_url": "./", "start_url": "../",
"theme_color": "#4F4F4F", "theme_color": "#4F4F4F",
"background_color": "#EBEBEB", "background_color": "#EBEBEB",
"orientation": "portrait" "orientation": "portrait"

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://alltubedownload.net/</loc>
<changefreq>yearly</changefreq>
<priority>1</priority>
</url>
<url>
<loc>https://alltubedownload.net/extractors</loc>
<changefreq>weekly</changefreq>
</url>
</urlset>

View File

@ -1 +0,0 @@
Sitemap: https://alltubedownload.net/resources/sitemap.xml

View File

@ -1 +1 @@
python-3.8.6 python-3.8.12

View File

@ -1,8 +1,8 @@
{include file='inc/head.tpl'} {extends file='page.tpl'}
<div class="wrapper"> {block name='main'}
<main class="main error"> <div class="error">
{include file="inc/logo.tpl"} {include file="inc/logo.tpl"}
<h2>{t}An error occurred{/t}</h2> <h2>{t}An error occurred{/t}</h2>
<p><i>{$error|escape|nl2br}</i></p> <p><i>{$error|escape|nl2br}</i></p>
</main> </div>
{include file='inc/footer.tpl'} {/block}

View File

@ -1,12 +1,12 @@
{include file='inc/head.tpl'} {extends file='page.tpl'}
{include file='inc/header.tpl'} {block name='main'}
{include file='inc/logo.tpl'} {include file='inc/logo.tpl'}
<h2 class="titre">{t}Supported websites{/t}</h2> <h2 class="titre">{t}Supported websites{/t}</h2>
<div class="tripleliste"> <div class="tripleliste">
<ul> <ul>
{foreach $extractors as $extractor} {foreach $extractors as $extractor}
<li>{$extractor}</li> <li>{$extractor}</li>
{/foreach} {/foreach}
</ul> </ul>
</div> </div>
{include file='inc/footer.tpl'} {/block}

View File

@ -1,18 +1,11 @@
</div>
<footer class="small-font"> <footer class="small-font">
<div class="footer_wrapper"> <div class="footer_wrapper">
{$dev="<a rel='author' target='blank' {include file='snippets/dev.tpl' assign=dev}
href='http://rudloff.pro/'>
Pierre Rudloff
</a>"}
{t params=['@dev'=>$dev]}Code by @dev{/t} {t params=['@dev'=>$dev]}Code by @dev{/t}
&middot; &middot;
{$designer="<a rel='author' target='blank' {include file='snippets/designer.tpl' assign=designer}
href='http://olivierhaquette.fr'>
Olivier Haquette
</a>"}
{t params=['@designer' => $designer]}Design by @designer{/t} {t params=['@designer' => $designer]}Design by @designer{/t}
&middot; &middot;
@ -23,15 +16,7 @@
&middot; &middot;
{$youtubedl="<a href='http://ytdl-org.github.io/youtube-dl/'> {include file='snippets/youtubedl.tpl' assign=youtubedl}
youtube-dl
</a>"}
{t params=['@youtubedl'=>$youtubedl]}Based on @youtubedl{/t} {t params=['@youtubedl'=>$youtubedl]}Based on @youtubedl{/t}
</div> </div>
</footer> </footer>
</div>
{if isset($debug_render)}
{$debug_render->render()}
{/if}
</body>
</html>

View File

@ -1,5 +1,3 @@
<!doctype html>
<html lang="{$locale->getLocale()->getBcp47()}">
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name=viewport content="width=device-width, initial-scale=1"/> <meta name=viewport content="width=device-width, initial-scale=1"/>
@ -11,7 +9,6 @@
<link rel="stylesheet" href="{base_url}/assets/open-sans/open-sans.css"/> <link rel="stylesheet" href="{base_url}/assets/open-sans/open-sans.css"/>
<link rel="stylesheet" href="{base_url}/css/style.css"/> <link rel="stylesheet" href="{base_url}/css/style.css"/>
<title>{$config->appName}{if isset($title)} - {$title|escape}{/if}</title> <title>{$config->appName}{if isset($title)} - {$title|escape}{/if}</title>
<link rel="canonical" href="{$canonical}"/>
<link rel="icon" href="{base_url}/img/favicon.png"/> <link rel="icon" href="{base_url}/img/favicon.png"/>
<meta property="og:title" content="{$config->appName}{if isset($title)} - {$title|escape}{/if}"/> <meta property="og:title" content="{$config->appName}{if isset($title)} - {$title|escape}{/if}"/>
<meta property="og:image" content="{base_url}/img/logo.png"/> <meta property="og:image" content="{base_url}/img/logo.png"/>
@ -27,5 +24,3 @@
{$debug_render->renderHead()} {$debug_render->renderHead()}
{/if} {/if}
</head> </head>
<body>
<div class="page {$class}">

View File

@ -27,4 +27,3 @@
</div> </div>
{/if} {/if}
</header> </header>
<div class="wrapper">

View File

@ -1,5 +1,7 @@
<h1 class="logobis"> <h1 class="logobis">
<a class="logocompatible" href="{path_for name="index"}"> <a class="logocompatible" href="{path_for name="index"}">
<span class="logocompatiblemask"><img src="{base_url}/img/logocompatiblemask.png" width="447" height="107" <span class="logocompatiblemask">
alt="{$config->appName}"/></span> {html_image file='img/logocompatiblemask.png' path_prefix={base_url}|cat:'/' alt=$config->appName}
</a></h1> </span>
</a>
</h1>

View File

@ -1,8 +1,8 @@
{include file='inc/head.tpl'} {extends file='page.tpl'}
{include file='inc/header.tpl'} {block name='main'}
<main class="main"> <div>
<div><img class="logo" src="{base_url}/img/logo.png" {html_image file='img/logo.png' path_prefix={base_url}|cat:'/' alt=$config->appName class="logo"}
alt="{$config->appName}" width="328" height="284"></div> </div>
<form action="{path_for name="info"}"> <form action="{path_for name="info"}">
<label class="labelurl" for="url"> <label class="labelurl" for="url">
{t}Copy here the URL of your video (YouTube, Dailymotion, etc.){/t} {t}Copy here the URL of your video (YouTube, Dailymotion, etc.){/t}
@ -11,7 +11,7 @@
<span class="URLinput_wrapper"> <span class="URLinput_wrapper">
<!-- We used to have an autofocus attribute on this field but it triggerd a very specific CSS bug: https://github.com/Rudloff/alltube/issues/117 --> <!-- We used to have an autofocus attribute on this field but it triggerd a very specific CSS bug: https://github.com/Rudloff/alltube/issues/117 -->
<input class="URLinput large-font" type="url" name="url" id="url" <input class="URLinput large-font" type="url" name="url" id="url"
required placeholder="http://example.com/video"/> required placeholder="https://example.com/video"/>
</span> </span>
{if $config->uglyUrls} {if $config->uglyUrls}
<input type="hidden" name="page" value="info"/> <input type="hidden" name="page" value="info"/>
@ -20,18 +20,23 @@
{if $config->convert} {if $config->convert}
<div class="mp3 small-font"> <div class="mp3 small-font">
<div class="mp3-inner"> <div class="mp3-inner">
<input type="checkbox" id="audio" class="audio" name="audio" {($config->defaultAudio) ? 'checked' : ''}> <input type="checkbox" id="audio" class="audio"
name="audio" {($config->defaultAudio) ? 'checked' : ''}>
<label for="audio"><span class="ui"></span> <label for="audio"><span class="ui"></span>
{t}Audio only (MP3){/t} {t}Audio only (MP3){/t}
</label> </label>
{if $config->convertSeek} {if $config->convertSeek}
<div class="seekOptions"> <div class="seekOptions">
<label for="from">{t}From{/t}</label> <input type="text" pattern="(\d+:)?(\d+:)?\d+(\.\d+)?" <label for="from">{t}From{/t}</label> <input type="text"
placeholder="HH:MM:SS" value="" name="from" pattern="(\d+:)?(\d+:)?\d+(\.\d+)?"
id="from"/> placeholder="HH:MM:SS" value=""
<label for="to">{t}to{/t}</label> <input type="text" pattern="(\d+:)?(\d+:)?\d+(\.\d+)?" name="from"
placeholder="HH:MM:SS" value="" name="to" id="to"/> id="from"/>
</div> <label for="to">{t}to{/t}</label> <input type="text"
pattern="(\d+:)?(\d+:)?\d+(\.\d+)?"
placeholder="HH:MM:SS" value="" name="to"
id="to"/>
</div>
{/if} {/if}
</div> </div>
</div> </div>
@ -44,6 +49,4 @@
<a class="bookmarklet small-font" <a class="bookmarklet small-font"
href="javascript:window.location='{$domain}{path_for name='info' queryParams=['url' => '%url%']}'.replace('%url%', encodeURIComponent(location.href));">{t}Bookmarklet{/t}</a> href="javascript:window.location='{$domain}{path_for name='info' queryParams=['url' => '%url%']}'.replace('%url%', encodeURIComponent(location.href));">{t}Bookmarklet{/t}</a>
</div> </div>
{/block}
</main>
{include file='inc/footer.tpl'}

View File

@ -1,111 +1,59 @@
{include file="inc/head.tpl"} {extends file='page.tpl'}
<div class="wrapper"> {block name='main'}
<div itemscope itemtype="http://schema.org/VideoObject"> <div itemscope itemtype="https://schema.org/VideoObject">
<main class="main"> {include file="inc/logo.tpl"}
{include file="inc/logo.tpl"} {include file='snippets/title.tpl' assign=title}
{$title="<i itemprop='name'> <p id="download_intro">
<a itemprop='url' id='video_link' {t params=['@title' => $title]}You are going to download @title.{/t}
href='{$video->webpage_url}'> </p>
{$video->title}</a></i>"} {if isset($video->thumbnail)}
<p id="download_intro"> {html_image file=$video->thumbnail itemprop="thumbnailUrl" class="thumb"}
{t params=['@title' => $title]}You are going to download @title.{/t} {/if}
</p> {if isset($video->description)}
{if isset($video->thumbnail)} <meta itemprop="description" content="{$video->description|escape}"/>
<img itemprop="thumbnailUrl" class="thumb" src="{$video->thumbnail}" alt=""/> {/if}
{if isset($video->upload_date)}
<meta itemprop="uploadDate" content="{$video->upload_date}"/>
{/if}
<br/>
<form action="{path_for name="download"}">
<input type="hidden" name="url" value="{$video->webpage_url}"/>
{if $config->uglyUrls}
<input type="hidden" name="page" value="download"/>
{/if} {/if}
{if isset($video->description)} {if isset($video->formats) && count($video->formats) > 1}
<meta itemprop="description" content="{$video->description|escape}"/> <h3><label for="format">{t}Available formats:{/t}</label></h3>
{*
To make the default generic formats translatable:
{t}Best{/t}
{t}Remux best video with best audio{/t}
{t}Worst{/t}
*}
{html_options name='format' options=$formats selected=$defaultFormat id="format" class="formats monospace"}
<br/>
<br/>
{/if} {/if}
{if isset($video->upload_date)} {if $config->stream}
<meta itemprop="uploadDate" content="{$video->upload_date}"/> <input type="checkbox" {if $config->stream !== 'ask'}checked{/if} name="stream" id="stream"/>
<label for="stream">{t}Stream the video through the server{/t}</label>
<br/>
<br/>
{/if} {/if}
<br/> {if $config->convertAdvanced}
<form action="{path_for name="download"}"> <input type="checkbox" name="customConvert" id="customConvert"/>
<input type="hidden" name="url" value="{$video->webpage_url}"/> <label for="customConvert">{t}Convert into a custom format:{/t}</label>
{if $config->uglyUrls} {html_options name='customFormat' values=$config->convertAdvancedFormats output=$config->convertAdvancedFormats
<input type="hidden" name="page" value="download"/> title="{t}Custom format{/t}" name="customFormat" aria-label="{t}Format to convert to{/t}"}
{/if} {t}with{/t}
{if isset($video->formats) && count($video->formats) > 1} <label for="customBitrate" class="sr-only">{t}Bit rate{/t}</label>
<h3><label for="format">{t}Available formats:{/t}</label></h3> <input type="number" value="{$config->audioBitrate}" title="{t}Custom bitrate{/t}"
<select name="format" id="format" class="formats monospace"> class="customBitrate"
<optgroup label="{t}Generic formats{/t}"> name="customBitrate" id="customBitrate" aria-describedby="customBitrateUnit"/>
{foreach $config->genericFormats as $format => $name} <span id="customBitrateUnit">{t}kbit/s audio{/t}</span>
{* <br/>
To make the default generic formats translatable: <br/>
{t}Best{/t} {/if}
{t}Remux best video with best audio{/t} <input class="downloadBtn" type="submit" value="{t}Download{/t}"/><br/>
{t}Worst{/t} </form>
*}
<option value="{$format}">{t}{$name}{/t}</option>
{/foreach}
</optgroup>
<optgroup label="{t}Detailed formats{/t}" class="monospace">
{foreach $video->formats as $format}
{if $config->stream || $format->protocol|in_array:array('http', 'https')}
{strip}
<option value="{$format->format_id}">
{$format->ext}
{for $foo=1 to (5 - ($format->ext|strlen))}
&nbsp;
{/for}
{if isset($format->width)}
{$format->width}x{$format->height}
{for $foo=1 to (10 - (("{$format->width}x{$format->height}")|strlen))}
&nbsp;
{/for}
{else}
{for $foo=1 to 10}
&nbsp;
{/for}
{/if}
{if isset($format->filesize)}
{($format->filesize/1000000)|round:2} MB
{for $foo=1 to (7 - (($format->filesize/1000000)|round:2|strlen))}
&nbsp;
{/for}
{else}
{for $foo=1 to 10}
&nbsp;
{/for}
{/if}
{if isset($format->format_note)}
{$format->format_note}
{/if}
&nbsp;({$format->format_id})
</option>
{/strip}
{/if}
{/foreach}
</optgroup>
</select>
<br/>
<br/>
{/if}
{if $config->stream}
<input type="checkbox" {if $config->stream !== 'ask'}checked{/if} name="stream" id="stream"/>
<label for="stream">{t}Stream the video through the server{/t}</label>
<br/>
<br/>
{/if}
{if $config->convertAdvanced}
<input type="checkbox" name="customConvert" id="customConvert"/>
<label for="customConvert">{t}Convert into a custom format:{/t}</label>
<select title="{t}Custom format{/t}" name="customFormat" aria-label="{t}Format to convert to{/t}">
{foreach $config->convertAdvancedFormats as $format}
<option>{$format}</option>
{/foreach}
</select>
{t}with{/t}
<label for="customBitrate" class="sr-only">{t}Bit rate{/t}</label>
<input type="number" value="{$config->audioBitrate}" title="{t}Custom bitrate{/t}"
class="customBitrate"
name="customBitrate" id="customBitrate" aria-describedby="customBitrateUnit"/>
<span id="customBitrateUnit">{t}kbit/s audio{/t}</span>
<br/>
<br/>
{/if}
<input class="downloadBtn" type="submit" value="{t}Download{/t}"/><br/>
</form>
</main>
</div> </div>
{include file="inc/footer.tpl"} {/block}

18
templates/page.tpl Normal file
View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="{$locale->getLocale()->getBcp47()}">
{include file='inc/head.tpl'}
<body>
<div class="page {$class}">
{include file='inc/header.tpl'}
<div class="wrapper">
<main class="main">
{block name="main"}{/block}
</main>
</div>
{include file='inc/footer.tpl'}
</div>
{if isset($debug_render)}
{$debug_render->render()}
{/if}
</body>
</html>

View File

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

View File

@ -1,44 +1,40 @@
{include file="inc/head.tpl"} {extends file='page.tpl'}
<div class="wrapper"> {block name='main'}
<main class="main"> {include file="inc/logo.tpl"}
{include file="inc/logo.tpl"}
{if isset($video->title)} {if isset($video->title)}
{$title="<i> {include file='snippets/title.tpl' assign=title}
<a href='{$video->webpage_url}'> <p>
{$video->title}</a> {t params=['@title'=>$title]}Videos extracted from @title:{/t}
</i>"} </p>
<p> {/if}
{t params=['@title'=>$title]}Videos extracted from @title:{/t}
</p>
{/if}
{if $config->stream} {if $config->stream}
<a href="{path_for name="download"}?url={$video->webpage_url}" class="downloadBtn">Download everything</a> <a href="{path_for name="download"}?url={$video->webpage_url}" class="downloadBtn">Download everything</a>
{/if} {/if}
{foreach $video->entries as $entry} {foreach $video->entries as $entry}
<div class="playlist-entry"> <div class="playlist-entry">
<h3 class="playlist-entry-title"><a target="_blank" href="{strip} <h3 class="playlist-entry-title">
<a target="_blank" href="{strip}
{if isset($entry->ie_key) and $entry->ie_key == Youtube and !filter_var($entry->url, FILTER_VALIDATE_URL)} {if isset($entry->ie_key) and $entry->ie_key == Youtube and !filter_var($entry->url, FILTER_VALIDATE_URL)}
https://www.youtube.com/watch?v= https://www.youtube.com/watch?v=
{/if} {/if}
{$entry->url} {$entry->url}
{/strip}"> {/strip}">
{if !isset($entry->title)} {if !isset($entry->title)}
{if $entry->ie_key == YoutubePlaylist} {if $entry->ie_key == YoutubePlaylist}
Playlist Playlist
{else}
Video
{/if}
{else} {else}
{$entry->title} Video
{/if} {/if}
</a></h3> {else}
<a target="_blank" class="downloadBtn" {$entry->title}
href="{path_for name="download"}?url={$entry->url}">{t}Download{/t}</a> {/if}
<a target="_blank" href="{path_for name="info"}?url={$entry->url}">{t}More options{/t}</a> </a>
</div> </h3>
{/foreach} <a target="_blank" class="downloadBtn"
href="{path_for name="download"}?url={$entry->url}">{t}Download{/t}</a>
</main> <a target="_blank" href="{path_for name="info"}?url={$entry->url}">{t}More options{/t}</a>
{include file="inc/footer.tpl"} </div>
{/foreach}
{/block}

View File

@ -0,0 +1,4 @@
<a rel="author" target="blank"
href="https://ographik.fr/">
Olivier Haquette
</a>

View File

@ -0,0 +1,4 @@
<a rel="author" target="blank"
href="https://rudloff.pro/">
Pierre Rudloff
</a>

View File

@ -0,0 +1,5 @@
<i itemprop="name">
<a itemprop="url" id="video_link"
href="{$video->webpage_url}">
{$video->title}</a>
</i>

View File

@ -0,0 +1,3 @@
<a href="https://ytdl-org.github.io/youtube-dl/">
youtube-dl
</a>

View File

@ -6,7 +6,9 @@
namespace Alltube\Test; namespace Alltube\Test;
use OndraM\CiDetector\CiDetector;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Util\Test;
/** /**
* Abstract class used by every test. * Abstract class used by every test.
@ -37,7 +39,11 @@ abstract class BaseTest extends TestCase
*/ */
protected function checkRequirements() protected function checkRequirements()
{ {
$annotations = $this->getAnnotations(); $ciDetector = new CiDetector();
$annotations = Test::parseTestMethodAnnotations(
static::class,
$this->getName()
);
$requires = []; $requires = [];
if (isset($annotations['class']['requires'])) { if (isset($annotations['class']['requires'])) {
@ -48,7 +54,7 @@ abstract class BaseTest extends TestCase
} }
foreach ($requires as $require) { foreach ($requires as $require) {
if ($require == 'download' && getenv('CI')) { if ($require == 'download' && $ciDetector->isCiDetected()) {
$this->markTestSkipped('Do not run tests that download videos on CI.'); $this->markTestSkipped('Do not run tests that download videos on CI.');
} }
} }

View File

@ -108,14 +108,6 @@ class DownloadControllerTest extends ControllerTest
public function testDownloadWithRtmpStream() public function testDownloadWithRtmpStream()
{ {
$this->markTestIncomplete('We need to find another RTMP video.'); $this->markTestIncomplete('We need to find another RTMP video.');
$config = $this->container->get('config');
$config->setOptions(['stream' => true]);
$this->assertRequestIsOk(
'download',
['url' => 'http://www.rtvnh.nl/video/131946', 'format' => 'rtmp-264']
);
} }
/** /**
@ -161,7 +153,7 @@ class DownloadControllerTest extends ControllerTest
*/ */
public function testDownloadWithMissingPassword() public function testDownloadWithMissingPassword()
{ {
$this->assertRequestIsClientError('download', ['url' => 'http://vimeo.com/68375962']); $this->assertRequestIsClientError('download', ['url' => 'https://vimeo.com/68375962']);
} }
/** /**
@ -172,7 +164,7 @@ class DownloadControllerTest extends ControllerTest
public function testDownloadWithError() public function testDownloadWithError()
{ {
$this->expectException(YoutubedlException::class); $this->expectException(YoutubedlException::class);
$this->getRequestResult('download', ['url' => 'http://example.com/foo']); $this->getRequestResult('download', ['url' => 'https://example.com/foo']);
} }
/** /**

View File

@ -186,12 +186,12 @@ class FrontControllerTest extends ControllerTest
* *
* @return void * @return void
* @requires download * @requires download
* @throws AlltubeLibraryException * @throws AlltubeLibraryException|InvalidURLException
*/ */
public function testInfoWithPassword() public function testInfoWithPassword()
{ {
$result = $this->controller->info( $result = $this->controller->info(
$this->container->get('request')->withQueryParams(['url' => 'http://vimeo.com/68375962']) $this->container->get('request')->withQueryParams(['url' => 'https://vimeo.com/68375962'])
->withParsedBody(['password' => 'youtube-dl']), ->withParsedBody(['password' => 'youtube-dl']),
$this->container->get('response') $this->container->get('response')
); );
@ -206,8 +206,8 @@ class FrontControllerTest extends ControllerTest
*/ */
public function testInfoWithMissingPassword() public function testInfoWithMissingPassword()
{ {
$this->assertRequestIsClientError('info', ['url' => 'http://vimeo.com/68375962']); $this->assertRequestIsClientError('info', ['url' => 'https://vimeo.com/68375962']);
$this->assertRequestIsClientError('info', ['url' => 'http://vimeo.com/68375962', 'audio' => true]); $this->assertRequestIsClientError('info', ['url' => 'https://vimeo.com/68375962', 'audio' => true]);
} }
/** /**

View File

@ -49,7 +49,7 @@ class JsonControllerTest extends ControllerTest
public function testJsonWithError() public function testJsonWithError()
{ {
$this->expectException(YoutubedlException::class); $this->expectException(YoutubedlException::class);
$this->getRequestResult('json', ['url' => 'http://example.com/foo']); $this->getRequestResult('json', ['url' => 'https://example.com/foo']);
} }
/** /**

View File

@ -79,7 +79,7 @@ class UglyRouterTest extends ContainerTest
public function testPathFor() public function testPathFor()
{ {
$this->assertEquals( $this->assertEquals(
'/?page=foo', '/?page=%2Ffoo',
$this->router->pathFor('foo', [], []) $this->router->pathFor('foo', [], [])
); );
} }
@ -93,7 +93,7 @@ class UglyRouterTest extends ContainerTest
{ {
$this->router->setBasePath('/bar'); $this->router->setBasePath('/bar');
$this->assertEquals( $this->assertEquals(
'/bar/?page=foo', '/bar/?page=%2Ffoo',
$this->router->pathFor('foo', [], []) $this->router->pathFor('foo', [], [])
); );
} }

View File

@ -104,7 +104,7 @@ class VideoTest extends ContainerTest
*/ */
public function testgetUrlWithPassword() public function testgetUrlWithPassword()
{ {
$video = new Video($this->downloader, 'http://vimeo.com/68375962', 'best', 'youtube-dl'); $video = new Video($this->downloader, 'https://vimeo.com/68375962', 'best', 'youtube-dl');
foreach ($video->getUrl() as $videoURL) { foreach ($video->getUrl() as $videoURL) {
$this->assertStringContainsString('vimeocdn.com', $videoURL); $this->assertStringContainsString('vimeocdn.com', $videoURL);
} }
@ -119,7 +119,7 @@ class VideoTest extends ContainerTest
public function testgetUrlWithMissingPassword() public function testgetUrlWithMissingPassword()
{ {
$this->expectException(PasswordException::class); $this->expectException(PasswordException::class);
$video = new Video($this->downloader, 'http://vimeo.com/68375962', $this->format); $video = new Video($this->downloader, 'https://vimeo.com/68375962', $this->format);
$video->getUrl(); $video->getUrl();
} }
@ -132,7 +132,7 @@ class VideoTest extends ContainerTest
public function testgetUrlWithWrongPassword() public function testgetUrlWithWrongPassword()
{ {
$this->expectException(WrongPasswordException::class); $this->expectException(WrongPasswordException::class);
$video = new Video($this->downloader, 'http://vimeo.com/68375962', 'best', 'foo'); $video = new Video($this->downloader, 'https://vimeo.com/68375962', 'best', 'foo');
$video->getUrl(); $video->getUrl();
} }
@ -174,7 +174,7 @@ class VideoTest extends ContainerTest
'googlevideo.com', 'googlevideo.com',
], ],
[ [
'http://www.bbc.co.uk/programmes/b039g8p7', 'bestaudio/best', 'https://www.bbc.co.uk/programmes/b039g8p7', 'bestaudio/best',
'Kaleidoscope_Leonard_Cohen-b039d07m', 'Kaleidoscope_Leonard_Cohen-b039d07m',
'flv', 'flv',
'bbcodspdns.fcod.llnwd.net', 'bbcodspdns.fcod.llnwd.net',
@ -247,7 +247,7 @@ class VideoTest extends ContainerTest
public function errorUrlProvider(): array public function errorUrlProvider(): array
{ {
return [ return [
['http://example.com/video'], ['https://example.com/video'],
]; ];
} }
@ -479,16 +479,11 @@ class VideoTest extends ContainerTest
* @param string $format Format * @param string $format Format
* *
* @return void * @return void
* @throws AlltubeLibraryException
* @dataProvider rtmpUrlProvider * @dataProvider rtmpUrlProvider
*/ */
public function testGetRtmpStream(string $url, string $format) public function testGetRtmpStream(string $url, string $format)
{ {
$this->markTestIncomplete('We need to find another RTMP video.'); $this->markTestIncomplete('We need to find another RTMP video.');
$video = new Video($this->downloader, $url, $format);
$this->assertStream($this->downloader->getRtmpStream($video));
} }
/** /**