diff --git a/classes/Config.php b/classes/Config.php
index d9ffcf0..4ee07c1 100644
--- a/classes/Config.php
+++ b/classes/Config.php
@@ -75,6 +75,13 @@ class Config
*/
public $stream = false;
+ /**
+ * Allow to remux video + audio?
+ *
+ * @var bool
+ */
+ public $remux = false;
+
/**
* YAML config file path.
*
diff --git a/classes/VideoDownload.php b/classes/VideoDownload.php
index 41b0cc0..a6b8873 100644
--- a/classes/VideoDownload.php
+++ b/classes/VideoDownload.php
@@ -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.
*
diff --git a/controllers/FrontController.php b/controllers/FrontController.php
index 55011be..98d6937 100644
--- a/controllers/FrontController.php
+++ b/controllers/FrontController.php
@@ -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(
diff --git a/templates/video.tpl b/templates/video.tpl
index 8872b1e..64ca555 100644
--- a/templates/video.tpl
+++ b/templates/video.tpl
@@ -34,6 +34,11 @@
Best ({$video->ext})
{/strip}
+ {if $remux}
+
+ {/if}
diff --git a/tests/FrontControllerTest.php b/tests/FrontControllerTest.php
index 2cc3a82..c964463 100644
--- a/tests/FrontControllerTest.php
+++ b/tests/FrontControllerTest.php
@@ -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.
*
diff --git a/tests/VideoDownloadTest.php b/tests/VideoDownloadTest.php
index bf15cfe..eeb85c2 100644
--- a/tests/VideoDownloadTest.php
+++ b/tests/VideoDownloadTest.php
@@ -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.
*