New remux feature (fixes #103)
This commit is contained in:
parent
b80b9c7b2e
commit
e6bbe54474
6 changed files with 169 additions and 16 deletions
|
@ -75,6 +75,13 @@ class Config
|
||||||
*/
|
*/
|
||||||
public $stream = false;
|
public $stream = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to remux video + audio?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $remux = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YAML config file path.
|
* YAML config file path.
|
||||||
*
|
*
|
||||||
|
|
|
@ -119,15 +119,19 @@ class VideoDownload
|
||||||
/**
|
/**
|
||||||
* Get URL of video from URL of page.
|
* 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 $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
|
* @param string $password Video password
|
||||||
*
|
*
|
||||||
* @return string URL of video
|
* @return array URLs of video
|
||||||
* */
|
* */
|
||||||
public function getURL($url, $format = null, $password = null)
|
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));
|
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.
|
* Get filename of audio from URL of page.
|
||||||
*
|
*
|
||||||
|
@ -155,14 +181,7 @@ class VideoDownload
|
||||||
* */
|
* */
|
||||||
public function getAudioFilename($url, $format = null, $password = null)
|
public function getAudioFilename($url, $format = null, $password = null)
|
||||||
{
|
{
|
||||||
return html_entity_decode(
|
return $this->getFileNameWithExtension('mp3', $url, $format, $password);
|
||||||
pathinfo(
|
|
||||||
$this->getFilename($url, $format, $password),
|
|
||||||
PATHINFO_FILENAME
|
|
||||||
).'.mp3',
|
|
||||||
ENT_COMPAT,
|
|
||||||
'ISO-8859-1'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -307,6 +326,31 @@ class VideoDownload
|
||||||
return popen($procBuilder->getProcess()->getCommandLine(), 'r');
|
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.
|
* Get video stream from an RTMP video.
|
||||||
*
|
*
|
||||||
|
|
|
@ -177,9 +177,9 @@ class FrontController
|
||||||
if ($this->config->stream) {
|
if ($this->config->stream) {
|
||||||
return $this->getStream($params['url'], 'mp3', $response, $request, $password);
|
return $this->getStream($params['url'], 'mp3', $response, $request, $password);
|
||||||
} else {
|
} 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) {
|
} catch (PasswordException $e) {
|
||||||
return $this->password($request, $response);
|
return $this->password($request, $response);
|
||||||
|
@ -234,6 +234,7 @@ class FrontController
|
||||||
'config' => $this->config,
|
'config' => $this->config,
|
||||||
'canonical' => $this->getCanonicalUrl($request),
|
'canonical' => $this->getCanonicalUrl($request),
|
||||||
'uglyUrls' => $this->config->uglyUrls,
|
'uglyUrls' => $this->config->uglyUrls,
|
||||||
|
'remux' => $this->config->remux,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -358,13 +359,33 @@ class FrontController
|
||||||
$this->sessionSegment->getFlash($params['url'])
|
$this->sessionSegment->getFlash($params['url'])
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$url = $this->download->getURL(
|
$urls = $this->download->getURL(
|
||||||
$params['url'],
|
$params['url'],
|
||||||
$format,
|
$format,
|
||||||
$this->sessionSegment->getFlash($params['url'])
|
$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) {
|
} catch (PasswordException $e) {
|
||||||
return $response->withRedirect(
|
return $response->withRedirect(
|
||||||
|
|
|
@ -34,6 +34,11 @@
|
||||||
Best ({$video->ext})
|
Best ({$video->ext})
|
||||||
{/strip}
|
{/strip}
|
||||||
</option>
|
</option>
|
||||||
|
{if $remux}
|
||||||
|
<option value="bestvideo+bestaudio">
|
||||||
|
Remux best video with best audio
|
||||||
|
</option>
|
||||||
|
{/if}
|
||||||
<option value="worst{$protocol}">
|
<option value="worst{$protocol}">
|
||||||
Worst
|
Worst
|
||||||
</option>
|
</option>
|
||||||
|
|
|
@ -361,6 +361,45 @@ class FrontControllerTest extends \PHPUnit_Framework_TestCase
|
||||||
$this->assertTrue($result->isOk());
|
$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.
|
* Test the redirect() function with a missing password.
|
||||||
*
|
*
|
||||||
|
|
|
@ -88,7 +88,7 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
|
||||||
public function testGetURL($url, $format, $filename, $extension, $domain)
|
public function testGetURL($url, $format, $filename, $extension, $domain)
|
||||||
{
|
{
|
||||||
$videoURL = $this->download->getURL($url, $format);
|
$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()
|
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[]
|
* @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()
|
public function m3uUrlProvider()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -390,6 +408,25 @@ class VideoDownloadTest extends \PHPUnit_Framework_TestCase
|
||||||
$this->assertFalse(feof($stream));
|
$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.
|
* Test getRtmpStream function.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue