diff --git a/classes/VideoDownload.php b/classes/VideoDownload.php
index d357b0b..08d3ecc 100644
--- a/classes/VideoDownload.php
+++ b/classes/VideoDownload.php
@@ -268,27 +268,52 @@ class VideoDownload
* @param int $audioBitrate Audio bitrate of the converted file
* @param string $filetype Filetype of the converted file
* @param bool $audioOnly True to return an audio-only file
+ * @param string $from Start the conversion at this time
+ * @param string $to End the conversion at this time
*
* @throws Exception If avconv/ffmpeg is missing
*
* @return Process Process
*/
- private function getAvconvProcess(stdClass $video, $audioBitrate, $filetype = 'mp3', $audioOnly = true)
- {
+ private function getAvconvProcess(
+ stdClass $video,
+ $audioBitrate,
+ $filetype = 'mp3',
+ $audioOnly = true,
+ $from = null,
+ $to = null
+ ) {
if (!$this->checkCommand([$this->config->avconv, '-version'])) {
throw new Exception(_('Can\'t find avconv or ffmpeg at ').$this->config->avconv.'.');
}
+ $durationRegex = '/(\d+:)?(\d+:)?(\d+)/';
+
if ($video->protocol == 'rtmp') {
- $rtmpArguments = $this->getRtmpArguments($video);
+ $beforeArguments = $this->getRtmpArguments($video);
} else {
- $rtmpArguments = [];
+ $beforeArguments = [];
}
if ($audioOnly) {
- $videoArguments = ['-vn'];
+ $afterArguments = ['-vn'];
} else {
- $videoArguments = [];
+ $afterArguments = [];
+ }
+
+ if (isset($from) && !empty($from)) {
+ if (!preg_match($durationRegex, $from)) {
+ throw new Exception(_('Invalid start time: ').$from.'.');
+ }
+ $afterArguments[] = '-ss';
+ $afterArguments[] = $from;
+ }
+ if (isset($to) && !empty($to)) {
+ if (!preg_match($durationRegex, $to)) {
+ throw new Exception(_('Invalid end time: ').$to.'.');
+ }
+ $afterArguments[] = '-to';
+ $afterArguments[] = $to;
}
$arguments = array_merge(
@@ -296,13 +321,13 @@ class VideoDownload
$this->config->avconv,
'-v', $this->config->avconvVerbosity,
],
- $rtmpArguments,
+ $beforeArguments,
[
'-i', $video->url,
'-f', $filetype,
'-b:a', $audioBitrate.'k',
],
- $videoArguments,
+ $afterArguments,
[
'pipe:1',
]
@@ -322,13 +347,15 @@ class VideoDownload
* @param string $url URL of page
* @param string $format Format to use for the video
* @param string $password Video password
+ * @param string $from Start the conversion at this time
+ * @param string $to End the conversion at this time
*
* @throws Exception If your try to convert and M3U8 video
* @throws Exception If the popen stream was not created correctly
*
* @return resource popen stream
*/
- public function getAudioStream($url, $format, $password = null)
+ public function getAudioStream($url, $format, $password = null, $from = null, $to = null)
{
$video = $this->getJSON($url, $format, $password);
@@ -336,13 +363,15 @@ class VideoDownload
throw new Exception(_('Conversion of playlists is not supported.'));
}
- if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) {
- throw new Exception(_('Conversion of M3U8 files is not supported.'));
- } elseif ($video->protocol == 'http_dash_segments') {
- throw new Exception(_('Conversion of DASH segments is not supported.'));
+ if (isset($video->protocol)) {
+ if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) {
+ throw new Exception(_('Conversion of M3U8 files is not supported.'));
+ } elseif ($video->protocol == 'http_dash_segments') {
+ throw new Exception(_('Conversion of DASH segments is not supported.'));
+ }
}
- $avconvProc = $this->getAvconvProcess($video, $this->config->audioBitrate);
+ $avconvProc = $this->getAvconvProcess($video, $this->config->audioBitrate, 'mp3', true, $from, $to);
$stream = popen($avconvProc->getCommandLine(), 'r');
diff --git a/controllers/FrontController.php b/controllers/FrontController.php
index 3d1e2f5..43fbf14 100644
--- a/controllers/FrontController.php
+++ b/controllers/FrontController.php
@@ -201,7 +201,50 @@ class FrontController
}
/**
- * Return the converted MP3 file.
+ * Return a converted MP3 file.
+ *
+ * @param Request $request PSR-7 request
+ * @param Response $response PSR-7 response
+ * @param array $params GET query parameters
+ * @param string $password Video password
+ *
+ * @return Response HTTP response
+ */
+ private function getConvertedAudioResponse(Request $request, Response $response, array $params, $password = null)
+ {
+ $response = $response->withHeader(
+ 'Content-Disposition',
+ 'attachment; filename="'.
+ $this->download->getAudioFilename($params['url'], 'bestaudio/best', $password).'"'
+ );
+ $response = $response->withHeader('Content-Type', 'audio/mpeg');
+
+ if ($request->isGet() || $request->isPost()) {
+ try {
+ $process = $this->download->getAudioStream(
+ $params['url'],
+ 'bestaudio/best',
+ $password,
+ $params['from'],
+ $params['to']
+ );
+ } catch (Exception $e) {
+ $process = $this->download->getAudioStream(
+ $params['url'],
+ $this->defaultFormat,
+ $password,
+ $params['from'],
+ $params['to']
+ );
+ }
+ $response = $response->withBody(new Stream($process));
+ }
+
+ return $response;
+ }
+
+ /**
+ * Return the MP3 file.
*
* @param Request $request PSR-7 request
* @param Response $response PSR-7 response
@@ -213,6 +256,10 @@ class FrontController
private function getAudioResponse(Request $request, Response $response, array $params, $password = null)
{
try {
+ if (isset($params['from']) || isset($params['to'])) {
+ throw new Exception('Force convert when we need to seek.');
+ }
+
if ($this->config->stream) {
return $this->getStream($params['url'], 'mp3', $response, $request, $password);
} else {
@@ -223,23 +270,7 @@ class FrontController
} catch (PasswordException $e) {
return $this->password($request, $response);
} catch (Exception $e) {
- $response = $response->withHeader(
- 'Content-Disposition',
- 'attachment; filename="'.
- $this->download->getAudioFilename($params['url'], 'bestaudio/best', $password).'"'
- );
- $response = $response->withHeader('Content-Type', 'audio/mpeg');
-
- if ($request->isGet() || $request->isPost()) {
- try {
- $process = $this->download->getAudioStream($params['url'], 'bestaudio/best', $password);
- } catch (Exception $e) {
- $process = $this->download->getAudioStream($params['url'], $this->defaultFormat, $password);
- }
- $response = $response->withBody(new Stream($process));
- }
-
- return $response;
+ return $this->getConvertedAudioResponse($request, $response, $params, $password);
}
}
diff --git a/css/style.css b/css/style.css
index 0dc7cd2..951ef4d 100644
--- a/css/style.css
+++ b/css/style.css
@@ -260,11 +260,10 @@ footer a:hover {
width:622px;
}
-.mp3 p {
+.mp3-inner {
padding:3px;
}
-
.audio:not(:checked),
.audio:checked {
left: -9999px;
@@ -273,7 +272,7 @@ footer a:hover {
.audio:not(:checked) + label,
.audio:checked + label {
cursor: pointer;
- line-height:22px;
+ line-height:20px;
padding-left: 82px;
position: relative;
}
@@ -373,7 +372,15 @@ footer a:hover {
width:73px;
}
+.seekOptions {
+ display: none;
+ margin-top: 15px;
+ text-align: center;
+}
+.audio:checked ~ .seekOptions {
+ display: block;
+}
/* Playlists */
diff --git a/templates/index.tpl b/templates/index.tpl
index 2a5420f..e293947 100644
--- a/templates/index.tpl
+++ b/templates/index.tpl
@@ -19,9 +19,16 @@
{if $config->convert}
-
+