New remux feature (fixes #103)

This commit is contained in:
Pierre Rudloff 2017-04-25 00:40:24 +02:00
parent b80b9c7b2e
commit e6bbe54474
6 changed files with 169 additions and 16 deletions

View File

@ -75,6 +75,13 @@ class Config
*/
public $stream = false;
/**
* Allow to remux video + audio?
*
* @var bool
*/
public $remux = false;
/**
* YAML config file path.
*

View File

@ -119,15 +119,19 @@ class VideoDownload
/**
* Get URL of video from URL of page.
*
* It generally returns only one URL.
* But it can return two URLs when multiple formats are specified
* (eg. bestvideo+bestaudio).
*
* @param string $url URL of page
* @param string $format Format to use for the video
* @param string $password Video password
*
* @return string URL of video
* @return array URLs of video
* */
public function getURL($url, $format = null, $password = null)
{
return $this->getProp($url, $format, 'get-url', $password);
return explode(PHP_EOL, $this->getProp($url, $format, 'get-url', $password));
}
/**
@ -144,6 +148,28 @@ class VideoDownload
return trim($this->getProp($url, $format, 'get-filename', $password));
}
/**
* Get filename of video with the specified extension
*
* @param string $extension New file extension
* @param string $url URL of page
* @param string $format Format to use for the video
* @param string $password Video password
*
* @return string Filename of extracted video with specified extension
*/
public function getFileNameWithExtension($extension, $url, $format = null, $password = null)
{
return html_entity_decode(
pathinfo(
$this->getFilename($url, $format, $password),
PATHINFO_FILENAME
).'.'.$extension,
ENT_COMPAT,
'ISO-8859-1'
);
}
/**
* Get filename of audio from URL of page.
*
@ -155,14 +181,7 @@ class VideoDownload
* */
public function getAudioFilename($url, $format = null, $password = null)
{
return html_entity_decode(
pathinfo(
$this->getFilename($url, $format, $password),
PATHINFO_FILENAME
).'.mp3',
ENT_COMPAT,
'ISO-8859-1'
);
return $this->getFileNameWithExtension('mp3', $url, $format, $password);
}
/**
@ -307,6 +326,31 @@ class VideoDownload
return popen($procBuilder->getProcess()->getCommandLine(), 'r');
}
/**
* Get an avconv stream to remux audio and video.
*
* @param array $urls URLs of the video ($urls[0]) and audio ($urls[1]) files
*
* @return resource popen stream
*/
public function getRemuxStream(array $urls)
{
$procBuilder = ProcessBuilder::create(
[
$this->config->avconv,
'-v', 'quiet',
'-i', $urls[0],
'-i', $urls[1],
'-c', 'copy',
'-map', '0:v:0 ',
'-map', '1:a:0',
'-f', 'matroska',
'pipe:1',
]
);
return popen($procBuilder->getProcess()->getCommandLine(), 'r');
}
/**
* Get video stream from an RTMP video.
*

View File

@ -177,9 +177,9 @@ class FrontController
if ($this->config->stream) {
return $this->getStream($params['url'], 'mp3', $response, $request, $password);
} else {
$url = $this->download->getURL($params['url'], 'mp3[protocol^=http]', $password);
$urls = $this->download->getURL($params['url'], 'mp3[protocol^=http]', $password);
return $response->withRedirect($url);
return $response->withRedirect($urls[0]);
}
} catch (PasswordException $e) {
return $this->password($request, $response);
@ -234,6 +234,7 @@ class FrontController
'config' => $this->config,
'canonical' => $this->getCanonicalUrl($request),
'uglyUrls' => $this->config->uglyUrls,
'remux' => $this->config->remux,
]
);
@ -358,13 +359,33 @@ class FrontController
$this->sessionSegment->getFlash($params['url'])
);
} else {
$url = $this->download->getURL(
$urls = $this->download->getURL(
$params['url'],
$format,
$this->sessionSegment->getFlash($params['url'])
);
if (count($urls) > 1) {
if (!$this->config->remux) {
throw new \Exception('You need to enable remux mode to merge two formats.');
}
$stream = $this->download->getRemuxStream($urls);
$response = $response->withHeader('Content-Type', 'video/x-matroska');
if ($request->isGet()) {
$response = $response->withBody(new Stream($stream));
}
return $response->withRedirect($url);
return $response->withHeader('Content-Disposition', 'attachment; filename="'.pathinfo(
$this->download->getFileNameWithExtension(
'mkv',
$params['url'],
$format,
$this->sessionSegment->getFlash($params['url'])
),
PATHINFO_FILENAME
).'.mkv"');
} else {
return $response->withRedirect($urls[0]);
}
}
} catch (PasswordException $e) {
return $response->withRedirect(

View File

@ -34,6 +34,11 @@
Best ({$video->ext})
{/strip}
</option>
{if $remux}
<option value="bestvideo+bestaudio">
Remux best video with best audio
</option>
{/if}
<option value="worst{$protocol}">
Worst
</option>

View File

@ -361,6 +361,45 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($result->isOk());
}
/**
* Test the redirect() function with a remuxed video.
*
* @return void
*/
public function testRedirectWithRemux()
{
$controller = new FrontController($this->container, new Config(['remux'=>true]));
$result = $controller->redirect(
$this->request->withQueryParams(
[
'url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU',
'format'=>'bestvideo+bestaudio'
]
),
$this->response
);
$this->assertTrue($result->isOk());
}
/**
* Test the redirect() function with a remuxed video but remux disabled.
*
* @return void
*/
public function testRedirectWithRemuxDisabled()
{
$result = $this->controller->redirect(
$this->request->withQueryParams(
[
'url'=>'https://www.youtube.com/watch?v=M7IpKCZ47pU',
'format'=>'bestvideo+bestaudio'
]
),
$this->response
);
$this->assertTrue($result->isServerError());
}
/**
* Test the redirect() function with a missing password.
*

View File

@ -88,7 +88,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
public function testGetURL($url, $format, $filename, $extension, $domain)
{
$videoURL = $this->download->getURL($url, $format);
$this->assertContains($domain, $videoURL);
$this->assertContains($domain, $videoURL[0]);
}
/**
@ -98,7 +98,8 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*/
public function testGetURLWithPassword()
{
$this->assertContains('vimeocdn.com', $this->download->getURL('http://vimeo.com/68375962', null, 'youtube-dl'));
$videoURL = $this->download->getURL('http://vimeo.com/68375962', null, 'youtube-dl');
$this->assertContains('vimeocdn.com', $videoURL[0]);
}
/**
@ -184,6 +185,23 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
*
* @return array[]
*/
public function remuxUrlProvider()
{
return [
[
'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'bestvideo+bestaudio',
"It's Not Me, It's You - Hearts Under Fire-M7IpKCZ47pU",
'mp4',
'googlevideo.com',
],
];
}
/**
* Provides URLs for remux tests.
*
* @return array[]
*/
public function m3uUrlProvider()
{
return [
@ -390,6 +408,25 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
$this->assertFalse(feof($stream));
}
/**
* Test getRemuxStream function.
*
* @param string $url URL
* @param string $format Format
*
* @return void
* @dataProvider remuxUrlProvider
*/
public function testGetRemuxStream($url, $format)
{
$urls = $this->download->getURL($url, $format);
if (count($urls) > 1) {
$stream = $this->download->getRemuxStream($urls);
$this->assertInternalType('resource', $stream);
$this->assertFalse(feof($stream));
}
}
/**
* Test getRtmpStream function.
*