feat: Add a way to trim the audio

This commit is contained in:
Pierre Rudloff 2018-07-03 19:47:35 +02:00
parent c80828f300
commit f1cf0a2cdc
4 changed files with 112 additions and 38 deletions

View file

@ -268,27 +268,52 @@ class VideoDownload
* @param int $audioBitrate Audio bitrate of the converted file * @param int $audioBitrate Audio bitrate of the converted file
* @param string $filetype Filetype of the converted file * @param string $filetype Filetype of the converted file
* @param bool $audioOnly True to return an audio-only 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 * @throws Exception If avconv/ffmpeg is missing
* *
* @return Process Process * @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'])) { if (!$this->checkCommand([$this->config->avconv, '-version'])) {
throw new Exception(_('Can\'t find avconv or ffmpeg at ').$this->config->avconv.'.'); throw new Exception(_('Can\'t find avconv or ffmpeg at ').$this->config->avconv.'.');
} }
$durationRegex = '/(\d+:)?(\d+:)?(\d+)/';
if ($video->protocol == 'rtmp') { if ($video->protocol == 'rtmp') {
$rtmpArguments = $this->getRtmpArguments($video); $beforeArguments = $this->getRtmpArguments($video);
} else { } else {
$rtmpArguments = []; $beforeArguments = [];
} }
if ($audioOnly) { if ($audioOnly) {
$videoArguments = ['-vn']; $afterArguments = ['-vn'];
} else { } 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( $arguments = array_merge(
@ -296,13 +321,13 @@ class VideoDownload
$this->config->avconv, $this->config->avconv,
'-v', $this->config->avconvVerbosity, '-v', $this->config->avconvVerbosity,
], ],
$rtmpArguments, $beforeArguments,
[ [
'-i', $video->url, '-i', $video->url,
'-f', $filetype, '-f', $filetype,
'-b:a', $audioBitrate.'k', '-b:a', $audioBitrate.'k',
], ],
$videoArguments, $afterArguments,
[ [
'pipe:1', 'pipe:1',
] ]
@ -322,13 +347,15 @@ class VideoDownload
* @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
* @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 your try to convert and M3U8 video
* @throws Exception If the popen stream was not created correctly * @throws Exception If the popen stream was not created correctly
* *
* @return resource popen stream * @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); $video = $this->getJSON($url, $format, $password);
@ -336,13 +363,15 @@ class VideoDownload
throw new Exception(_('Conversion of playlists is not supported.')); throw new Exception(_('Conversion of playlists is not supported.'));
} }
if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) { if (isset($video->protocol)) {
throw new Exception(_('Conversion of M3U8 files is not supported.')); if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) {
} elseif ($video->protocol == 'http_dash_segments') { throw new Exception(_('Conversion of M3U8 files is not supported.'));
throw new Exception(_('Conversion of DASH segments 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'); $stream = popen($avconvProc->getCommandLine(), 'r');

View file

@ -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 Request $request PSR-7 request
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
@ -213,6 +256,10 @@ class FrontController
private function getAudioResponse(Request $request, Response $response, array $params, $password = null) private function getAudioResponse(Request $request, Response $response, array $params, $password = null)
{ {
try { try {
if (isset($params['from']) || isset($params['to'])) {
throw new Exception('Force convert when we need to seek.');
}
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 {
@ -223,23 +270,7 @@ class FrontController
} catch (PasswordException $e) { } catch (PasswordException $e) {
return $this->password($request, $response); return $this->password($request, $response);
} catch (Exception $e) { } catch (Exception $e) {
$response = $response->withHeader( return $this->getConvertedAudioResponse($request, $response, $params, $password);
'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;
} }
} }

View file

@ -260,11 +260,10 @@ footer a:hover {
width:622px; width:622px;
} }
.mp3 p { .mp3-inner {
padding:3px; padding:3px;
} }
.audio:not(:checked), .audio:not(:checked),
.audio:checked { .audio:checked {
left: -9999px; left: -9999px;
@ -273,7 +272,7 @@ footer a:hover {
.audio:not(:checked) + label, .audio:not(:checked) + label,
.audio:checked + label { .audio:checked + label {
cursor: pointer; cursor: pointer;
line-height:22px; line-height:20px;
padding-left: 82px; padding-left: 82px;
position: relative; position: relative;
} }
@ -373,7 +372,15 @@ footer a:hover {
width:73px; width:73px;
} }
.seekOptions {
display: none;
margin-top: 15px;
text-align: center;
}
.audio:checked ~ .seekOptions {
display: block;
}
/* Playlists */ /* Playlists */

View file

@ -19,9 +19,16 @@
<input class="downloadBtn large-font" type="submit" value="{t}Download{/t}" /><br/> <input class="downloadBtn large-font" type="submit" value="{t}Download{/t}" /><br/>
{if $config->convert} {if $config->convert}
<div class="mp3 small-font"> <div class="mp3 small-font">
<p><input type="checkbox" id="audio" class="audio" name="audio"> <div class="mp3-inner">
<label for="audio"><span class="ui"></span> <input type="checkbox" id="audio" class="audio" name="audio">
{t}Audio only (MP3){/t}</label></p> <label for="audio"><span class="ui"></span>
{t}Audio only (MP3){/t}
</label>
<div class="seekOptions">
From <input type="text" placeholder="00:00:00" value="" name="from" />
to <input type="text" placeholder="00:22:30" value="" name="to" />
</div>
</div>
</div> </div>
{/if} {/if}
</div> </div>