Add support for password protected videos

This commit is contained in:
Pierre Rudloff 2016-10-20 23:01:31 +02:00
parent 621ccfb491
commit e34b01f2c4
9 changed files with 196 additions and 29 deletions

View File

@ -0,0 +1,13 @@
<?php
/**
* PasswordException class
*/
namespace Alltube;
/**
* Exception thrown when a video requires a password
*/
class PasswordException extends \Exception
{
}

View File

@ -71,13 +71,14 @@ class VideoDownload
/** /**
* Get a property from youtube-dl. * Get a property from youtube-dl.
* *
* @param string $url URL to parse * @param string $url URL to parse
* @param string $format Format * @param string $format Format
* @param string $prop Property * @param string $prop Property
* @param string $password Video password
* *
* @return string * @return string
*/ */
private function getProp($url, $format = null, $prop = 'dump-json') private function getProp($url, $format = null, $prop = 'dump-json', $password = null)
{ {
$this->procBuilder->setArguments( $this->procBuilder->setArguments(
[ [
@ -88,10 +89,21 @@ class VideoDownload
if (isset($format)) { if (isset($format)) {
$this->procBuilder->add('-f '.$format); $this->procBuilder->add('-f '.$format);
} }
if (isset($password)) {
$this->procBuilder->add('--video-password');
$this->procBuilder->add($password);
}
$process = $this->procBuilder->getProcess(); $process = $this->procBuilder->getProcess();
$process->run(); $process->run();
if (!$process->isSuccessful()) { if (!$process->isSuccessful()) {
throw new \Exception($process->getErrorOutput()); $errorOutput = trim($process->getErrorOutput());
if ($errorOutput == 'ERROR: This video is protected by a password, use the --video-password option') {
throw new PasswordException($errorOutput);
} elseif (substr($errorOutput, 0, 21) == 'ERROR: Wrong password') {
throw new \Exception('Wrong password');
} else {
throw new \Exception($errorOutput);
}
} else { } else {
return $process->getOutput(); return $process->getOutput();
} }
@ -100,40 +112,43 @@ class VideoDownload
/** /**
* Get all information about a video. * Get all information about a video.
* *
* @param string $url URL of page * @param string $url URL of page
* @param string $format Format to use for the video * @param string $format Format to use for the video
* @param string $password Video password
* *
* @return object Decoded JSON * @return object Decoded JSON
* */ * */
public function getJSON($url, $format = null) public function getJSON($url, $format = null, $password = null)
{ {
return json_decode($this->getProp($url, $format, 'dump-json')); return json_decode($this->getProp($url, $format, 'dump-json', $password));
} }
/** /**
* Get URL of video from URL of page. * Get URL of video from URL of page.
* *
* @param string $url URL of page * @param string $url URL of page
* @param string $format Format to use for the video * @param string $format Format to use for the video
* @param string $password Video password
* *
* @return string URL of video * @return string URL of video
* */ * */
public function getURL($url, $format = null) public function getURL($url, $format = null, $password = null)
{ {
return $this->getProp($url, $format, 'get-url'); return $this->getProp($url, $format, 'get-url', $password);
} }
/** /**
* Get filename of video file from URL of page. * Get filename of video file from URL of page.
* *
* @param string $url URL of page * @param string $url URL of page
* @param string $format Format to use for the video * @param string $format Format to use for the video
* @param string $password Video password
* *
* @return string Filename of extracted video * @return string Filename of extracted video
* */ * */
public function getFilename($url, $format = null) public function getFilename($url, $format = null, $password = null)
{ {
return trim($this->getProp($url, $format, 'get-filename')); return trim($this->getProp($url, $format, 'get-filename', $password));
} }
/** /**

View File

@ -11,7 +11,8 @@
"symfony/yaml": "~3.1.0", "symfony/yaml": "~3.1.0",
"symfony/process": "~3.1.0", "symfony/process": "~3.1.0",
"ptachoire/process-builder-chain": "~1.2.0", "ptachoire/process-builder-chain": "~1.2.0",
"rudloff/smarty-plugin-noscheme": "~0.1.0" "rudloff/smarty-plugin-noscheme": "~0.1.0",
"aura/session": "~2.1.0"
}, },
"require-dev": { "require-dev": {
"symfony/var-dumper": "~3.1.0", "symfony/var-dumper": "~3.1.0",

66
composer.lock generated
View File

@ -4,9 +4,71 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "2814570fa83cedc8e079c1d236a787d2", "hash": "7feb22c9a83e389562253bd1e7389080",
"content-hash": "91057608d6f29b8de8a9761bb419f19c", "content-hash": "0ca3a07c96a159c3a44ae007b56a9fbf",
"packages": [ "packages": [
{
"name": "aura/session",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/auraphp/Aura.Session.git",
"reference": "7d2f7d41ad693970b5b6b83facca0961d3378883"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/auraphp/Aura.Session/zipball/7d2f7d41ad693970b5b6b83facca0961d3378883",
"reference": "7d2f7d41ad693970b5b6b83facca0961d3378883",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"aura/di": "~2.0"
},
"suggest": {
"ext-mcrypt": "Mcrypt generates the next best secure CSRF tokens.",
"ext-openssl": "OpenSSL generates the best secure CSRF tokens.",
"ircmaxell/random-lib": "A Library For Generating Secure Random Numbers",
"paragonie/random_compat": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7"
},
"type": "library",
"extra": {
"aura": {
"type": "library",
"config": {
"common": "Aura\\Session\\_Config\\Common"
}
}
},
"autoload": {
"psr-4": {
"Aura\\Session\\": "src/",
"Aura\\Session\\_Config\\": "config/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Aura.Session Contributors",
"homepage": "https://github.com/auraphp/Aura.Session/contributors"
}
],
"description": "Provides session management functionality, including lazy session starting, session segments, next-request-only (\"flash\") values, and CSRF tools.",
"homepage": "https://github.com/auraphp/Aura.Session",
"keywords": [
"csrf",
"flash",
"flash message",
"session",
"sessions"
],
"time": "2016-10-03 20:28:32"
},
{ {
"name": "container-interop/container-interop", "name": "container-interop/container-interop",
"version": "1.1.0", "version": "1.1.0",

View File

@ -6,6 +6,7 @@ namespace Alltube\Controller;
use Alltube\Config; use Alltube\Config;
use Alltube\VideoDownload; use Alltube\VideoDownload;
use Alltube\PasswordException;
use Interop\Container\ContainerInterface; use Interop\Container\ContainerInterface;
use Slim\Container; use Slim\Container;
use Slim\Http\Request; use Slim\Http\Request;
@ -48,6 +49,9 @@ class FrontController
$this->config = Config::getInstance(); $this->config = Config::getInstance();
$this->download = new VideoDownload(); $this->download = new VideoDownload();
$this->container = $container; $this->container = $container;
$session_factory = new \Aura\Session\SessionFactory;
$session = $session_factory->newInstance($_COOKIE);
$this->sessionSegment = $session->getSegment('Alltube\Controller\FrontController');
} }
/** /**
@ -98,6 +102,28 @@ class FrontController
} }
} }
/**
* Display a password prompt
* @param Request $request PSR-7 request
* @param Response $response PSR-7 response
*
* @return Response HTTP response
*/
public function password(Request $request, Response $response)
{
if ($this->container instanceof Container) {
$this->container->view->render(
$response,
'password.tpl',
[
'class' => 'password',
'title' => 'Password prompt',
'description' => 'You need a password in order to download this video with Alltube Download',
]
);
}
}
/** /**
* Dislay information about the video. * Dislay information about the video.
* *
@ -109,8 +135,11 @@ class FrontController
public function video(Request $request, Response $response) public function video(Request $request, Response $response)
{ {
$params = $request->getQueryParams(); $params = $request->getQueryParams();
$this->config = Config::getInstance();
if (isset($params['url'])) { if (isset($params['url'])) {
$password = $request->getParam('password');
if (isset($password)) {
$this->sessionSegment->setFlash($params['url'], $password);
}
if (isset($params['audio'])) { if (isset($params['audio'])) {
try { try {
$url = $this->download->getURL($params['url'], 'mp3[protocol^=http]'); $url = $this->download->getURL($params['url'], 'mp3[protocol^=http]');
@ -132,7 +161,11 @@ class FrontController
return $response; return $response;
} }
} else { } else {
$video = $this->download->getJSON($params['url']); try {
$video = $this->download->getJSON($params['url'], null, $password);
} catch (PasswordException $e) {
return $this->password($request, $response);
}
if ($this->container instanceof Container) { if ($this->container instanceof Container) {
$this->container->view->render( $this->container->view->render(
$response, $response,
@ -190,9 +223,11 @@ class FrontController
$params = $request->getQueryParams(); $params = $request->getQueryParams();
if (isset($params['url'])) { if (isset($params['url'])) {
try { try {
$url = $this->download->getURL($params['url'], $params['format']); $url = $this->download->getURL($params['url'], $request->getParam('format'), $this->sessionSegment->getFlash($params['url']));
return $response->withRedirect($url); return $response->withRedirect($url);
} catch (PasswordException $e) {
return $response->withRedirect($this->container->get('router')->pathFor('video').'?url='.urlencode($params['url']));
} catch (\Exception $e) { } catch (\Exception $e) {
$response->getBody()->write($e->getMessage()); $response->getBody()->write($e->getMessage());

View File

@ -35,7 +35,7 @@ $app->get(
'/extractors', '/extractors',
[$controller, 'extractors'] [$controller, 'extractors']
)->setName('extractors'); )->setName('extractors');
$app->get( $app->any(
'/video', '/video',
[$controller, 'video'] [$controller, 'video']
)->setName('video'); )->setName('video');

13
templates/password.tpl Normal file
View File

@ -0,0 +1,13 @@
{include file='inc/head.tpl'}
<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>
<form action="" method="POST">
<input class="URLinput" type="password" name="password" title="Video password" />
<br/><br/>
<input class="downloadBtn" type="submit" value="Download" />
</form>
</div>
{include file='inc/footer.tpl'}

View File

@ -28,11 +28,7 @@
<optgroup label="Generic formats"> <optgroup label="Generic formats">
<option value="best[protocol^=http]"> <option value="best[protocol^=http]">
{strip} {strip}
Best ({$video->ext} Best ({$video->ext})
{if isset($video->filesize)}
{$video->filesize}
{/if}
)
{/strip} {/strip}
</option> </option>
<option value="worst[protocol^=http]"> <option value="worst[protocol^=http]">

View File

@ -89,6 +89,38 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
$this->assertContains($domain, $videoURL); $this->assertContains($domain, $videoURL);
} }
/**
* Test getURL function with a protected video
*
* @return void
*/
public function testGetURLWithPassword()
{
$this->assertContains('vimeocdn.com', $this->download->getURL('http://vimeo.com/68375962', null, 'youtube-dl'));
}
/**
* Test getURL function with a protected video and no password.
*
* @return void
* @expectedException \Alltube\PasswordException
*/
public function testGetURLWithMissingPassword()
{
$this->download->getURL('http://vimeo.com/68375962');
}
/**
* Test getURL function with a protected video and a wrong password.
*
* @return void
* @expectedException Exception
*/
public function testGetURLWithWrongPassword()
{
$this->download->getURL('http://vimeo.com/68375962', null, 'foo');
}
/** /**
* Test getURL function errors. * Test getURL function errors.
* *